diff options
Diffstat (limited to 'src/main/java/io/github/moulberry/notenoughupdates/infopanes/HTMLInfoPane.java')
| -rw-r--r-- | src/main/java/io/github/moulberry/notenoughupdates/infopanes/HTMLInfoPane.java | 316 |
1 files changed, 316 insertions, 0 deletions
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/infopanes/HTMLInfoPane.java b/src/main/java/io/github/moulberry/notenoughupdates/infopanes/HTMLInfoPane.java new file mode 100644 index 00000000..6469ff1c --- /dev/null +++ b/src/main/java/io/github/moulberry/notenoughupdates/infopanes/HTMLInfoPane.java @@ -0,0 +1,316 @@ +package io.github.moulberry.notenoughupdates.infopanes; + +import info.bliki.htmlcleaner.TagNode; +import info.bliki.wiki.filter.Encoder; +import info.bliki.wiki.model.Configuration; +import info.bliki.wiki.model.ImageFormat; +import info.bliki.wiki.model.WikiModel; +import info.bliki.wiki.tags.HTMLBlockTag; +import info.bliki.wiki.tags.HTMLTag; +import info.bliki.wiki.tags.IgnoreTag; +import io.github.moulberry.notenoughupdates.AllowEmptyHTMLTag; +import io.github.moulberry.notenoughupdates.NEUManager; +import io.github.moulberry.notenoughupdates.NEUOverlay; +import io.github.moulberry.notenoughupdates.Utils; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.gui.ScaledResolution; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.texture.DynamicTexture; +import net.minecraft.util.EnumChatFormatting; +import net.minecraft.util.ResourceLocation; + +import javax.imageio.ImageIO; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.*; +import java.nio.charset.CharsetEncoder; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +public class HTMLInfoPane extends TextInfoPane { + + private static WikiModel wikiModel; + + private final int ZOOM_FACTOR = 2; + private final int IMAGE_WIDTH = 400; + private final int EXT_WIDTH = 100; + + private ResourceLocation imageTexture = null; + private BufferedImage imageTemp = null; + private int imageHeight = 0; + private int imageWidth = 0; + + static { + Configuration conf = new Configuration(); + conf.addTokenTag("img", new HTMLTag("img")); + conf.addTokenTag("code", new HTMLTag("code")); + conf.addTokenTag("span", new AllowEmptyHTMLTag("span")); + conf.addTokenTag("table", new HTMLBlockTag("table", Configuration.SPECIAL_BLOCK_TAGS+"span|")); + conf.addTokenTag("infobox", new IgnoreTag("infobox")); + conf.addTokenTag("tabber", new IgnoreTag("tabber")); + conf.addTokenTag("kbd", new HTMLTag("kbd")); + wikiModel = new WikiModel(conf,"https://hypixel-skyblock.fandom.com/wiki/Special:Filepath/${image}", + "https://hypixel-skyblock.fandom.com/wiki/${title}") { + { + TagNode.addAllowedAttribute("style"); + TagNode.addAllowedAttribute("src"); + } + + protected String createImageName(ImageFormat imageFormat) { + String imageName = imageFormat.getFilename(); + if (imageName.endsWith(".svg")) { + imageName += ".png"; + } + imageName = Encoder.encodeUrl(imageName); + if (replaceColon()) { + imageName = imageName.replace(':', '/'); + } + return imageName; + } + + public void parseInternalImageLink(String imageNamespace, String rawImageLink) { + rawImageLink = rawImageLink.replaceFirst("\\|x([0-9]+)px", "\\|$1x$1px"); + if(!rawImageLink.split("\\|")[0].toLowerCase().endsWith(".jpg")) { + super.parseInternalImageLink(imageNamespace, rawImageLink); + } + } + }; + } + + public static HTMLInfoPane createFromWikiUrl(NEUOverlay overlay, NEUManager manager, String name, String wikiUrl) { + File f = manager.getWebFile(wikiUrl); + if(f == null) { + return new HTMLInfoPane(overlay, manager, "error", "Failed to load wiki url: "+ wikiUrl); + }; + + StringBuilder sb = new StringBuilder(); + try(BufferedReader br = new BufferedReader(new InputStreamReader( + new FileInputStream(f), StandardCharsets.UTF_8))) { + String l; + while((l = br.readLine()) != null){ + sb.append(l).append("\n"); + } + } catch(IOException e) { + return new HTMLInfoPane(overlay, manager, "error", "Failed to load wiki url: "+ wikiUrl); + } + return createFromWiki(overlay, manager, name, sb.toString()); + } + + public static HTMLInfoPane createFromWiki(NEUOverlay overlay, NEUManager manager, String name, String wiki) { + String[] split = wiki.split("</infobox>"); + wiki = split[split.length - 1]; //Remove everything before infobox + wiki = wiki.split("<span class=\"navbox-vde\">")[0]; //Remove navbox + wiki = wiki.split("<table class=\"navbox mw-collapsible\"")[0]; + wiki = "__NOTOC__\n" + wiki; //Remove TOC + try (PrintWriter out = new PrintWriter(new File(manager.configLocation, "debug/parsed.txt"))) { + out.println(wiki); + } catch (IOException e) { + } + String html; + try { + html = wikiModel.render(wiki); + } catch(IOException e) { + return new HTMLInfoPane(overlay, manager, "error", "Could not render wiki."); + } + try (PrintWriter out = new PrintWriter(new File(manager.configLocation, "debug/html.txt"))) { + out.println(html); + } catch (IOException e) { + } + return new HTMLInfoPane(overlay, manager, name, html); + } + + public HTMLInfoPane(NEUOverlay overlay, NEUManager manager, String name, String html) { + super(overlay, manager, name, ""); + this.title = name; + + File cssFile = new File(manager.configLocation, "wikia.css"); + File wkHtmlToImage = new File(manager.configLocation, "wkhtmltox/bin/wkhtmltoimage"); + File input = new File(manager.configLocation, "tmp/input.html"); + String outputFileName = name.replaceAll("(?i)\\u00A7.", "") + .replaceAll("[^a-zA-Z0-9_\\-]", "_"); + File output = new File(manager.configLocation, "tmp/"+ + outputFileName+".png"); + File outputExt = new File(manager.configLocation, "tmp/"+ + outputFileName+"_ext.png"); + + input.deleteOnExit(); + output.deleteOnExit(); + + File tmp = new File(manager.configLocation, "tmp"); + if(!tmp.exists()) { + tmp.mkdir(); + } + + if(output.exists()) { + try { + imageTemp = ImageIO.read(output); + text = EnumChatFormatting.RED+"Creating dynamic texture."; + } catch(IOException e) { + e.printStackTrace(); + text = EnumChatFormatting.RED+"Failed to read image."; + return; + } + } else { + html = "<div id=\"mw-content-text\" lang=\"en\" dir=\"ltr\" class=\"mw-content-ltr mw-content-text\">"+html+"</div>"; + html = "<div id=\"WikiaArticle\" class=\"WikiaArticle\">"+html+"</div>"; + html = "<link rel=\"stylesheet\" href=\"file:///"+cssFile.getAbsolutePath()+"\">\n"+html; + + try(PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter( + new FileOutputStream(input), StandardCharsets.UTF_8)), false)) { + + out.println(encodeNonAscii(html)); + } catch(IOException e) {} + + + ExecutorService ste = Executors.newSingleThreadExecutor(); + try { + text = EnumChatFormatting.GRAY+"Rendering webpage (" + name + EnumChatFormatting.RESET+ + EnumChatFormatting.GRAY+"), please wait..."; + + Runtime runtime = Runtime.getRuntime(); + Process p = runtime.exec("\""+wkHtmlToImage.getAbsolutePath() + "\" --width "+ + IMAGE_WIDTH*ZOOM_FACTOR+" --transparent --zoom "+ZOOM_FACTOR+" \"" + input.getAbsolutePath() + + "\" \"" + output.getAbsolutePath() + "\""); + /*Process p2 = runtime.exec("\""+wkHtmlToImage.getAbsolutePath() + "\" --width "+ + (IMAGE_WIDTH+EXT_WIDTH)*ZOOM_FACTOR+" --transparent --zoom "+ZOOM_FACTOR+" \"" + input.getAbsolutePath() + + "\" \"" + outputExt.getAbsolutePath() + "\"");*/ + ste.submit(() -> { + try { + if(p.waitFor(15, TimeUnit.SECONDS)) { + //if(p2.waitFor(5, TimeUnit.SECONDS)) { + if(overlay.getActiveInfoPane() != this) return; + + try { + imageTemp = ImageIO.read(output); + /*BufferedImage imageReg = ImageIO.read(output); + BufferedImage imageExt = ImageIO.read(outputExt); + ArrayList<Integer[]> pixels = new ArrayList<>(); + + int skip = IMAGE_WIDTH/EXT_WIDTH+1; + + for(int y=0; y<imageReg.getHeight(); y++) { + pixels.add(new Integer[IMAGE_WIDTH*ZOOM_FACTOR]); + if(new Color(imageReg.getRGB(IMAGE_WIDTH*ZOOM_FACTOR-1, y), true).getAlpha() == 0) { + for(int x=0; x<IMAGE_WIDTH*ZOOM_FACTOR; x++) { + pixels.get(y)[x] = imageReg.getRGB(x, y); + } + } else { + for(int x=0; x<(IMAGE_WIDTH+EXT_WIDTH)*ZOOM_FACTOR; x++) { + int x2 = x*IMAGE_WIDTH/(IMAGE_WIDTH+EXT_WIDTH); + int y2 = y*(IMAGE_WIDTH+EXT_WIDTH)/IMAGE_WIDTH; + pixels.get(y)[x2] = imageExt.getRGB(x, y2); + } + } + } + imageTemp = new BufferedImage(IMAGE_WIDTH*ZOOM_FACTOR, pixels.size(), imageReg.getType()); + for(int y=0; y<pixels.size(); y++) { + for(int x=0; x<IMAGE_WIDTH*ZOOM_FACTOR; x++) { + int col = pixels.get(y)[x]; + imageTemp.setRGB(x, y, col); + } + }*/ + text = EnumChatFormatting.RED+"Creating dynamic texture."; + } catch(IOException e) { + e.printStackTrace(); + text = EnumChatFormatting.RED+"Failed to read image."; + return; + } + } else { + if(overlay.getActiveInfoPane() != this) return; + + text = EnumChatFormatting.RED+"Webpage render timed out (>15sec). Maybe it's too large?"; + } + } catch(Exception e) { + e.printStackTrace(); + } + }); + } catch(IOException e) { + e.printStackTrace(); + text = EnumChatFormatting.RED+"Failed to exec webpage renderer."; + } finally { + ste.shutdown(); + } + } + } + + @Override + public void render(int width, int height, Color bg, Color fg, ScaledResolution scaledresolution, int mouseX, int mouseY) { + if(imageTemp != null && imageTexture == null) { + DynamicTexture tex = new DynamicTexture(imageTemp); + imageTexture = Minecraft.getMinecraft().getTextureManager().getDynamicTextureLocation( + "notenoughupdates/informationPaneImage", tex); + imageHeight = imageTemp.getHeight(); + imageWidth = imageTemp.getWidth(); + } + if(imageTexture == null) { + super.render(width, height, bg, fg, scaledresolution, mouseX, mouseY); + return; + } + + FontRenderer fr = Minecraft.getMinecraft().fontRendererObj; + + int paneWidth = (int)(width/3*overlay.getWidthMult()); + int rightSide = (int)(width*overlay.getInfoPaneOffsetFactor()); + int leftSide = rightSide - paneWidth; + + int titleLen = fr.getStringWidth(title); + fr.drawString(title, (leftSide+rightSide-titleLen)/2, overlay.BOX_PADDING + 5, Color.WHITE.getRGB()); + + drawRect(leftSide+overlay.BOX_PADDING-5, overlay.BOX_PADDING-5, rightSide-overlay.BOX_PADDING+5, + height-overlay.BOX_PADDING+5, bg.getRGB()); + + int imageW = paneWidth - overlay.BOX_PADDING*2; + float scaleF = IMAGE_WIDTH*ZOOM_FACTOR/(float)imageW; + + Minecraft.getMinecraft().getTextureManager().bindTexture(imageTexture); + GlStateManager.color(1f, 1f, 1f, 1f); + if(height-overlay.BOX_PADDING*3 < imageHeight/scaleF) { + if(scrollHeight.getValue() > imageHeight/scaleF-height+overlay.BOX_PADDING*3) { + scrollHeight.setValue((int)(imageHeight/scaleF-height+overlay.BOX_PADDING*3)); + } + int yScroll = scrollHeight.getValue(); + + float vMin = yScroll/(imageHeight/scaleF); + float vMax = (yScroll+height-overlay.BOX_PADDING*3)/(imageHeight/scaleF); + Utils.drawTexturedRect(leftSide+overlay.BOX_PADDING, overlay.BOX_PADDING*2, imageW, + height-overlay.BOX_PADDING*3, + 0, 1, vMin, vMax); + } else { + scrollHeight.setValue(0); + + Utils.drawTexturedRect(leftSide+overlay.BOX_PADDING, overlay.BOX_PADDING*2, imageW, + (int)(imageHeight/scaleF)); + } + GlStateManager.bindTexture(0); + } + + @Override + public void keyboardInput() { + } + + @Override + public void mouseInput(int width, int height, int mouseX, int mouseY, boolean mouseDown) { + super.mouseInput(width, height, mouseX, mouseY, mouseDown); + } + + //From https://stackoverflow.com/questions/1760766/how-to-convert-non-supported-character-to-html-entity-in-java + public String encodeNonAscii(String c) { + StringBuilder buf = new StringBuilder(c.length()); + CharsetEncoder enc = StandardCharsets.US_ASCII.newEncoder(); + for (int idx = 0; idx < c.length(); ++idx) { + char ch = c.charAt(idx); + if (enc.canEncode(ch)) + buf.append(ch); + else { + buf.append("&#"); + buf.append((int) ch); + buf.append(';'); + } + } + return buf.toString(); + } +} |
