From e1cdaf7b769abc115f2c2eb569c2bae775b2ede9 Mon Sep 17 00:00:00 2001 From: syeyoung Date: Fri, 20 Jan 2023 01:50:26 +0900 Subject: - RichText mojang pls don't sue me Signed-off-by: syeyoung --- .../syeyoung/dungeonsguide/mod/DungeonsGuide.java | 2 + .../mod/guiv2/elements/richtext/BreakWord.java | 23 ++ .../guiv2/elements/richtext/BrokenWordData.java | 31 +++ .../mod/guiv2/elements/richtext/FlatTextSpan.java | 180 +++++++++++++ .../mod/guiv2/elements/richtext/RichLine.java | 33 +++ .../mod/guiv2/elements/richtext/RichText.java | 206 ++++++++++++++ .../mod/guiv2/elements/richtext/TextSpan.java | 38 +++ .../mod/guiv2/elements/richtext/TextStyle.java | 48 ++++ .../richtext/fonts/DefaultFontRenderer.java | 300 +++++++++++++++++++++ .../elements/richtext/fonts/FontRenderer.java | 30 +++ .../guiv2/elements/richtext/shaders/Shader.java | 24 ++ .../richtext/shaders/SingleColorShader.java | 40 +++ 12 files changed, 955 insertions(+) create mode 100644 mod/src/main/java/kr/syeyoung/dungeonsguide/mod/guiv2/elements/richtext/BreakWord.java create mode 100644 mod/src/main/java/kr/syeyoung/dungeonsguide/mod/guiv2/elements/richtext/BrokenWordData.java create mode 100644 mod/src/main/java/kr/syeyoung/dungeonsguide/mod/guiv2/elements/richtext/FlatTextSpan.java create mode 100644 mod/src/main/java/kr/syeyoung/dungeonsguide/mod/guiv2/elements/richtext/RichLine.java create mode 100644 mod/src/main/java/kr/syeyoung/dungeonsguide/mod/guiv2/elements/richtext/RichText.java create mode 100644 mod/src/main/java/kr/syeyoung/dungeonsguide/mod/guiv2/elements/richtext/TextSpan.java create mode 100644 mod/src/main/java/kr/syeyoung/dungeonsguide/mod/guiv2/elements/richtext/TextStyle.java create mode 100644 mod/src/main/java/kr/syeyoung/dungeonsguide/mod/guiv2/elements/richtext/fonts/DefaultFontRenderer.java create mode 100644 mod/src/main/java/kr/syeyoung/dungeonsguide/mod/guiv2/elements/richtext/fonts/FontRenderer.java create mode 100644 mod/src/main/java/kr/syeyoung/dungeonsguide/mod/guiv2/elements/richtext/shaders/Shader.java create mode 100644 mod/src/main/java/kr/syeyoung/dungeonsguide/mod/guiv2/elements/richtext/shaders/SingleColorShader.java (limited to 'mod/src/main/java/kr') diff --git a/mod/src/main/java/kr/syeyoung/dungeonsguide/mod/DungeonsGuide.java b/mod/src/main/java/kr/syeyoung/dungeonsguide/mod/DungeonsGuide.java index ad27c7c2..f09ba949 100755 --- a/mod/src/main/java/kr/syeyoung/dungeonsguide/mod/DungeonsGuide.java +++ b/mod/src/main/java/kr/syeyoung/dungeonsguide/mod/DungeonsGuide.java @@ -35,6 +35,7 @@ import kr.syeyoung.dungeonsguide.mod.events.annotations.EventHandlerRegistry; import kr.syeyoung.dungeonsguide.mod.events.listener.DungeonListener; import kr.syeyoung.dungeonsguide.mod.events.listener.PacketListener; import kr.syeyoung.dungeonsguide.mod.features.FeatureRegistry; +import kr.syeyoung.dungeonsguide.mod.guiv2.elements.richtext.fonts.DefaultFontRenderer; import kr.syeyoung.dungeonsguide.mod.overlay.OverlayManager; import kr.syeyoung.dungeonsguide.mod.party.PartyManager; import kr.syeyoung.dungeonsguide.mod.resources.DGTexturePack; @@ -408,6 +409,7 @@ public class DungeonsGuide implements DGInterface { @Override public void onResourceReload(IResourceManager a) { GLCursors.setupCursors(); + DefaultFontRenderer.DEFAULT_RENDERER.onResourceManagerReload(); } private boolean showedStartUpGuide; diff --git a/mod/src/main/java/kr/syeyoung/dungeonsguide/mod/guiv2/elements/richtext/BreakWord.java b/mod/src/main/java/kr/syeyoung/dungeonsguide/mod/guiv2/elements/richtext/BreakWord.java new file mode 100644 index 00000000..54f058a6 --- /dev/null +++ b/mod/src/main/java/kr/syeyoung/dungeonsguide/mod/guiv2/elements/richtext/BreakWord.java @@ -0,0 +1,23 @@ +/* + * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod + * Copyright (C) 2023 cyoung06 (syeyoung) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package kr.syeyoung.dungeonsguide.mod.guiv2.elements.richtext; + +public enum BreakWord { + ALL, WORD +} diff --git a/mod/src/main/java/kr/syeyoung/dungeonsguide/mod/guiv2/elements/richtext/BrokenWordData.java b/mod/src/main/java/kr/syeyoung/dungeonsguide/mod/guiv2/elements/richtext/BrokenWordData.java new file mode 100644 index 00000000..0f9a7e32 --- /dev/null +++ b/mod/src/main/java/kr/syeyoung/dungeonsguide/mod/guiv2/elements/richtext/BrokenWordData.java @@ -0,0 +1,31 @@ +/* + * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod + * Copyright (C) 2023 cyoung06 (syeyoung) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package kr.syeyoung.dungeonsguide.mod.guiv2.elements.richtext; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data @AllArgsConstructor +public class BrokenWordData { + private FlatTextSpan first; + private FlatTextSpan second; + private boolean broken; + + private double firstWidth; +} diff --git a/mod/src/main/java/kr/syeyoung/dungeonsguide/mod/guiv2/elements/richtext/FlatTextSpan.java b/mod/src/main/java/kr/syeyoung/dungeonsguide/mod/guiv2/elements/richtext/FlatTextSpan.java new file mode 100644 index 00000000..f4ba08c8 --- /dev/null +++ b/mod/src/main/java/kr/syeyoung/dungeonsguide/mod/guiv2/elements/richtext/FlatTextSpan.java @@ -0,0 +1,180 @@ +/* + * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod + * Copyright (C) 2023 cyoung06 (syeyoung) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package kr.syeyoung.dungeonsguide.mod.guiv2.elements.richtext; + +public class FlatTextSpan { + public final TextStyle textStyle; + + public final char[] value; + + public FlatTextSpan(TextStyle textStyle, char[] value) { + this.textStyle = textStyle; + this.value = value; + } + + public double getWidth() { + double sum =0; + for (char c : value) { + sum += textStyle.getFontRenderer().getWidth(c, textStyle); + } + return sum; + } + public double getHeight() { + return textStyle.getSize() * textStyle.getLineHeight(); + } + public double getBaseline() { + return textStyle.getSize() * (textStyle.getLineHeight()-1) + textStyle.getFontRenderer().getBaselineHeight(textStyle); + } + + public BrokenWordData breakWord(double remainingWidth, double nextLineWidth, BreakWord breakWord) { + if (breakWord == BreakWord.WORD) { + // prefer to break on words + double scaledRemainingWidth = remainingWidth; + double scaledNextLineWidth = nextLineWidth; + + double totalWidth = 0; + double wordWidth = 0; + double charWidth = 0; + int wordStart = 0; + int potentialBreak = -1; + + int endIdx = value.length; + for (int i = 0; i < value.length; i++) { + char character = value[i]; + charWidth = textStyle.getFontRenderer().getWidth(character, textStyle); + + totalWidth += charWidth; + wordWidth += charWidth; + + if (character == ' ' || character == '\n') { + if (potentialBreak == -1) { + } else if (scaledNextLineWidth < wordWidth) { + // Break at potential, word is greater than next line + endIdx = potentialBreak; + break; + } else { + // Delegate to next line. + endIdx = wordStart; + break; + } + + // Force break. + if (character == '\n') { + endIdx = i; + break; + } + + // Since adding space exceeded remaining width, break without adding space. + if (totalWidth > scaledRemainingWidth) { + endIdx = i; + break; + } + + // reset states + wordStart = i + 1; + potentialBreak = -1; + wordWidth = 0; + } + + if (totalWidth > scaledRemainingWidth && potentialBreak == -1) { + potentialBreak = i; + } + } + + char[] first = new char[endIdx]; + System.arraycopy(value, 0, first, 0, endIdx); + + char[] second = null; + if (endIdx != value.length) { + int startRealWord = -1; + for (int i = endIdx; i< value.length; i++) { + if (value[i] == ' ' || value[i] == '\n') continue; + startRealWord = i; + } + if (startRealWord != -1) { + second = new char[value.length - startRealWord]; + System.arraycopy(value, startRealWord, second, 0, second.length); + } + } + + FlatTextSpan flatTextSpan = new FlatTextSpan(textStyle, first); + FlatTextSpan secondSpan = null; + if (second != null) + secondSpan = new FlatTextSpan(textStyle, second); + + + double resultingWidth = 0; + for (char c : first) { + resultingWidth += textStyle.getFontRenderer().getWidth(c, textStyle); + } + + return new BrokenWordData(flatTextSpan, secondSpan, endIdx != value.length, resultingWidth); + } else { + // break anywhere + double scaledRemainingWidth = remainingWidth; + + double totalWidth = 0; + double effectiveWidth = 0; + + int endIdx = value.length; + for (int i = 0; i < value.length; i++) { + char character = value[i]; + double charWidth = textStyle.getFontRenderer().getWidth(character, textStyle); + + totalWidth += charWidth; + + if (character == '\n') { + // Force break. + endIdx = i; + break; + } + + if (totalWidth > scaledRemainingWidth) { + // break here. + endIdx = i; + break; + } + effectiveWidth += charWidth; + } + + char[] first = new char[endIdx]; + System.arraycopy(value, 0, first, 0, endIdx); + + char[] second = null; + if (endIdx != value.length) { + int startRealWord = -1; + for (int i = endIdx; i< value.length; i++) { + if (value[i] == ' ' || value[i] == '\n') continue; + startRealWord = i; + } + if (startRealWord != -1) { + second = new char[value.length - startRealWord]; + System.arraycopy(value, startRealWord, second, 0, second.length); + } + } + + FlatTextSpan flatTextSpan = new FlatTextSpan(textStyle, first); + FlatTextSpan secondSpan = null; + if (second != null) + secondSpan = new FlatTextSpan(textStyle, second); + + return new BrokenWordData(flatTextSpan, secondSpan, endIdx != value.length, effectiveWidth); + } + } +} diff --git a/mod/src/main/java/kr/syeyoung/dungeonsguide/mod/guiv2/elements/richtext/RichLine.java b/mod/src/main/java/kr/syeyoung/dungeonsguide/mod/guiv2/elements/richtext/RichLine.java new file mode 100644 index 00000000..334bc749 --- /dev/null +++ b/mod/src/main/java/kr/syeyoung/dungeonsguide/mod/guiv2/elements/richtext/RichLine.java @@ -0,0 +1,33 @@ +/* + * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod + * Copyright (C) 2023 cyoung06 (syeyoung) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package kr.syeyoung.dungeonsguide.mod.guiv2.elements.richtext; + +import lombok.AllArgsConstructor; +import lombok.Data; + +import java.util.List; + +@Data @AllArgsConstructor +public class RichLine { + private List lineElements; + + private double width; + private double height; + private double baseline; +} diff --git a/mod/src/main/java/kr/syeyoung/dungeonsguide/mod/guiv2/elements/richtext/RichText.java b/mod/src/main/java/kr/syeyoung/dungeonsguide/mod/guiv2/elements/richtext/RichText.java new file mode 100644 index 00000000..6822a27d --- /dev/null +++ b/mod/src/main/java/kr/syeyoung/dungeonsguide/mod/guiv2/elements/richtext/RichText.java @@ -0,0 +1,206 @@ +/* + * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod + * Copyright (C) 2023 cyoung06 (syeyoung) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package kr.syeyoung.dungeonsguide.mod.guiv2.elements.richtext; + +import kr.syeyoung.dungeonsguide.mod.guiv2.BindableAttribute; +import kr.syeyoung.dungeonsguide.mod.guiv2.DomElement; +import kr.syeyoung.dungeonsguide.mod.guiv2.Widget; +import kr.syeyoung.dungeonsguide.mod.guiv2.layouter.Layouter; +import kr.syeyoung.dungeonsguide.mod.guiv2.primitive.ConstraintBox; +import kr.syeyoung.dungeonsguide.mod.guiv2.primitive.Size; +import kr.syeyoung.dungeonsguide.mod.guiv2.renderer.Renderer; +import kr.syeyoung.dungeonsguide.mod.guiv2.renderer.RenderingContext; +import lombok.Setter; + +import java.util.*; + +public class RichText extends Widget implements Layouter, Renderer { + private TextSpan rootSpan; + private BreakWord breakWord; + private boolean takeAllSpace = false; + @Setter + private TextAlign align; + + + private List richLines = new LinkedList<>(); + public static enum TextAlign { + LEFT, CENTER, RIGHT + } + + public RichText(TextSpan textSpan, BreakWord breakWord, boolean takeAllSpace, TextAlign align) { + this.rootSpan = textSpan; + this.breakWord = breakWord; + this.takeAllSpace = takeAllSpace; + this.align = align; + } + + public void setRootSpan(TextSpan rootSpan) { + this.rootSpan = rootSpan; + this.getDomElement().requestRelayout(); + } + + public void setBreakWord(BreakWord breakWord) { + this.breakWord = breakWord; + this.getDomElement().requestRelayout(); + } + + public void setTakeAllSpace(boolean takeAllSpace) { + this.takeAllSpace = takeAllSpace; + this.getDomElement().requestRelayout(); + } + + @Override + public Size layout(DomElement buildContext, ConstraintBox constraintBox) { + LinkedList flatTextSpans = new LinkedList<>(); + rootSpan.flattenTextSpan(flatTextSpans::add); + + LinkedList lines = new LinkedList<>(); + LinkedList line = new LinkedList<>(); + double remaining = constraintBox.getMaxWidth(); + double usedUp = 0; + double maxHeight = 0; + double maxBaseline = 0; + + double sumHeight = 0; + double maxWidth = 0; + + while (!flatTextSpans.isEmpty()) { + FlatTextSpan first = flatTextSpans.pollFirst(); + + BrokenWordData brokenWordData = first.breakWord(remaining, constraintBox.getMaxWidth(), breakWord); + remaining -= brokenWordData.getFirstWidth(); + usedUp += brokenWordData.getFirstWidth(); + line.add(brokenWordData.getFirst()); + + maxHeight = Math.max(maxHeight, first.getHeight()); + maxBaseline = Math.max(maxBaseline, first.getBaseline()); + + if (brokenWordData.getSecond() != null) { + flatTextSpans.addFirst(brokenWordData.getSecond()); + } + + if (brokenWordData.isBroken()) { + lines.add(new RichLine(line, usedUp, maxHeight, maxBaseline)); + line.clear(); + maxWidth = Math.max(maxWidth, usedUp); + sumHeight += maxHeight; + + remaining = constraintBox.getMaxWidth(); + usedUp = 0; + maxHeight = 0; + maxBaseline = 0; + } + } + if (!line.isEmpty()) { + lines.add(new RichLine(line, usedUp, maxHeight, maxBaseline)); + maxWidth = Math.max(maxWidth, usedUp); + sumHeight += maxHeight; + } + + richLines = lines; + + + return new Size(takeAllSpace ? constraintBox.getMaxWidth() : maxWidth, sumHeight); + } + + @Override + public double getMaxIntrinsicWidth(DomElement buildContext, double height) { + LinkedList flatTextSpans = new LinkedList<>(); + rootSpan.flattenTextSpan(flatTextSpans::add); + + LinkedList line = new LinkedList<>(); + double usedUp = 0; + + double maxWidth = 0; + while (!flatTextSpans.isEmpty()) { + FlatTextSpan first = flatTextSpans.pollFirst(); + BrokenWordData brokenWordData = first.breakWord(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, breakWord); + usedUp += brokenWordData.getFirstWidth(); + line.add(brokenWordData.getFirst()); + + if (brokenWordData.getSecond() != null) { + flatTextSpans.addFirst(brokenWordData.getSecond()); + } + + if (brokenWordData.isBroken()) { + maxWidth = Math.max(maxWidth, usedUp); + usedUp = 0; + } + } + if (!line.isEmpty()) { + maxWidth = Math.max(maxWidth, usedUp); + } + + return maxWidth; + } + + @Override + public double getMaxIntrinsicHeight(DomElement buildContext, double width) { + LinkedList flatTextSpans = new LinkedList<>(); + rootSpan.flattenTextSpan(flatTextSpans::add); + double remaining = width; + double maxHeight = 0; + double sumHeight = 0; + while (!flatTextSpans.isEmpty()) { + FlatTextSpan first = flatTextSpans.pollFirst(); + + BrokenWordData brokenWordData = first.breakWord(remaining, width, breakWord); + remaining -= brokenWordData.getFirstWidth(); + + maxHeight = Math.max(maxHeight, first.getHeight()); + + if (brokenWordData.getSecond() != null) { + flatTextSpans.addFirst(brokenWordData.getSecond()); + } + + if (brokenWordData.isBroken()) { + remaining = width; + sumHeight += maxHeight; + maxHeight = 0; + } + } + sumHeight += maxHeight; + return sumHeight; + } + + @Override + public List build(DomElement buildContext) { + return Collections.emptyList(); + } + + @Override + public void doRender(int absMouseX, int absMouseY, double relMouseX, double relMouseY, float partialTicks, RenderingContext context, DomElement buildContext) { + double x = 0; + double y = 0; + double width = buildContext.getSize().getWidth(); + double currentScale = buildContext.getAbsBounds().getWidth() / width; + for (RichLine richLine : richLines) { + if (align == TextAlign.RIGHT) + x = width - richLine.getWidth(); + else if (align == TextAlign.CENTER) + x = (width - richLine.getWidth()) / 2; + for (FlatTextSpan lineElement : richLine.getLineElements()) { + lineElement.textStyle.getFontRenderer() + .render(lineElement, x, y + richLine.getBaseline() - lineElement.getBaseline(), currentScale); + x += lineElement.getWidth(); + } + y += richLine.getHeight(); + } + } +} diff --git a/mod/src/main/java/kr/syeyoung/dungeonsguide/mod/guiv2/elements/richtext/TextSpan.java b/mod/src/main/java/kr/syeyoung/dungeonsguide/mod/guiv2/elements/richtext/TextSpan.java new file mode 100644 index 00000000..fde3e4d0 --- /dev/null +++ b/mod/src/main/java/kr/syeyoung/dungeonsguide/mod/guiv2/elements/richtext/TextSpan.java @@ -0,0 +1,38 @@ +/* + * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod + * Copyright (C) 2023 cyoung06 (syeyoung) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package kr.syeyoung.dungeonsguide.mod.guiv2.elements.richtext; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +public class TextSpan { + private TextStyle textStyle; + private String text; + private List children = new ArrayList<>(); + public TextSpan(TextStyle textStyle, String text) { + this.textStyle = textStyle; + this.text = text; + } + + public void flattenTextSpan(Consumer appender) { + appender.accept(new FlatTextSpan(textStyle, text.toCharArray())); + children.forEach(a -> a.flattenTextSpan(appender)); + } +} diff --git a/mod/src/main/java/kr/syeyoung/dungeonsguide/mod/guiv2/elements/richtext/TextStyle.java b/mod/src/main/java/kr/syeyoung/dungeonsguide/mod/guiv2/elements/richtext/TextStyle.java new file mode 100644 index 00000000..eb49baa2 --- /dev/null +++ b/mod/src/main/java/kr/syeyoung/dungeonsguide/mod/guiv2/elements/richtext/TextStyle.java @@ -0,0 +1,48 @@ +/* + * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod + * Copyright (C) 2023 cyoung06 (syeyoung) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package kr.syeyoung.dungeonsguide.mod.guiv2.elements.richtext; + +import kr.syeyoung.dungeonsguide.mod.guiv2.elements.richtext.fonts.FontRenderer; +import kr.syeyoung.dungeonsguide.mod.guiv2.elements.richtext.shaders.Shader; +import lombok.Data; + +@Data +public class TextStyle { + public Double size; + public Double lineHeight; + + public boolean bold; + public boolean italics; + public boolean strikeThrough; + public boolean underline; + public boolean outline; + public boolean shadow; + + public Shader backgroundShader; + public Shader textShader; + public Shader strikeThroughShader; + public Shader underlineShader; + public Shader outlineShader; + public Shader shadowShader; + + + + public TextStyle parent; + public FontRenderer fontRenderer; +} diff --git a/mod/src/main/java/kr/syeyoung/dungeonsguide/mod/guiv2/elements/richtext/fonts/DefaultFontRenderer.java b/mod/src/main/java/kr/syeyoung/dungeonsguide/mod/guiv2/elements/richtext/fonts/DefaultFontRenderer.java new file mode 100644 index 00000000..245fc6d2 --- /dev/null +++ b/mod/src/main/java/kr/syeyoung/dungeonsguide/mod/guiv2/elements/richtext/fonts/DefaultFontRenderer.java @@ -0,0 +1,300 @@ +/* + * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod + * Copyright (C) 2023 cyoung06 (syeyoung) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package kr.syeyoung.dungeonsguide.mod.guiv2.elements.richtext.fonts; + +import kr.syeyoung.dungeonsguide.mod.guiv2.elements.richtext.FlatTextSpan; +import kr.syeyoung.dungeonsguide.mod.guiv2.elements.richtext.TextStyle; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.client.renderer.WorldRenderer; +import net.minecraft.client.renderer.texture.TextureUtil; +import net.minecraft.client.renderer.vertex.DefaultVertexFormats; +import net.minecraft.client.renderer.vertex.VertexFormat; +import net.minecraft.client.renderer.vertex.VertexFormatElement; +import net.minecraft.util.ResourceLocation; +import org.apache.commons.io.IOUtils; +import org.lwjgl.opengl.GL11; + +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.util.List; + +public class DefaultFontRenderer implements FontRenderer { + public static DefaultFontRenderer DEFAULT_RENDERER = new DefaultFontRenderer(); + + private static final ResourceLocation[] unicodePageLocations = new ResourceLocation[256]; + protected int[] charWidth = new int[256]; + public int FONT_HEIGHT = 9; + protected byte[] glyphData = new byte[65536]; + protected final ResourceLocation locationFontTexture = new ResourceLocation("textures/font/ascii.png"); + + public DefaultFontRenderer() { + readGlyphSizes(); + } + + public void onResourceManagerReload() { + this.readFontTexture(); + this.readGlyphSizes(); + } + + private void readFontTexture() { + BufferedImage bufferedimage; + try { + bufferedimage = TextureUtil.readBufferedImage( + Minecraft.getMinecraft().getResourceManager().getResource(this.locationFontTexture).getInputStream()); + } catch (IOException var17) { + throw new RuntimeException(var17); + } + + int i = bufferedimage.getWidth(); + int j = bufferedimage.getHeight(); + int[] aint = new int[i * j]; + bufferedimage.getRGB(0, 0, i, j, aint, 0, i); + int k = j / 16; + int l = i / 16; + int i1 = 1; + float f = 8.0F / (float)l; + + for(int j1 = 0; j1 < 256; ++j1) { + int k1 = j1 % 16; + int l1 = j1 / 16; + if (j1 == 32) { + this.charWidth[j1] = 3 + i1; + } + + int i2; + for(i2 = l - 1; i2 >= 0; --i2) { + int j2 = k1 * l + i2; + boolean flag = true; + + for(int k2 = 0; k2 < k && flag; ++k2) { + int l2 = (l1 * l + k2) * i; + if ((aint[j2 + l2] >> 24 & 255) != 0) { + flag = false; + } + } + + if (!flag) { + break; + } + } + + ++i2; + this.charWidth[j1] = (int)(0.5 + (double)((float)i2 * f)) + i1; + } + + } + + private void readGlyphSizes() { + InputStream inputstream = null; + + try { + inputstream = Minecraft.getMinecraft().getResourceManager().getResource(new ResourceLocation("font/glyph_sizes.bin")).getInputStream(); + inputstream.read(this.glyphData); + } catch (IOException var6) { + throw new RuntimeException(var6); + } finally { + IOUtils.closeQuietly(inputstream); + } + + } + + private ResourceLocation getUnicodePageLocation(int page) { + if (unicodePageLocations[page] == null) { + unicodePageLocations[page] = new ResourceLocation(String.format("textures/font/unicode_page_%02x.png", page)); + } + + return unicodePageLocations[page]; + } + + @Override + public double getWidth(char text, TextStyle textStyle) { + double val; + if (text == ' ') { + val = 4; + } else { + int i = "ÀÁÂÈÊËÍÓÔÕÚßãõğİıŒœŞşŴŵžȇ\u0000\u0000\u0000\u0000\u0000\u0000\u0000 !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u0000ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜø£Ø׃áíóúñѪº¿®¬½¼¡«»░▒▓│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀αβΓπΣσμτΦΘΩδ∞∅∈∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■\u0000".indexOf(text); + if (text > 0 && i != -1) { + val = this.charWidth[i]; + } else if (this.glyphData[text] != 0) { + int texStart = this.glyphData[text] >>> 4; + int texEnd = this.glyphData[text] & 15 + 1; + val = (texEnd - texStart) / 2.0 + 1; + } else { + val = 0; + } + } + return (val + (textStyle.isBold() ? 1 : 0)) * textStyle.getSize() / 8; + } + + @Override + public double getBaselineHeight(TextStyle textStyle) { + return 7 * textStyle.getSize() / 8.0; + } + + public void render(WorldRenderer worldRenderer, FlatTextSpan lineElement, double x, double y, boolean isShadow) { + double startX = x; + if (isShadow) lineElement.textStyle.getShadowShader().useShader(); + + if (!isShadow) lineElement.textStyle.getTextShader().useShader(); + GlStateManager.enableTexture2D(); + worldRenderer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); + for (char c : lineElement.value) { + x += renderChar(worldRenderer, x, y+1, c, lineElement.textStyle); + } + if (!isShadow) lineElement.textStyle.getTextShader().freeShader(); + draw(worldRenderer); + + GlStateManager.disableTexture2D(); + if (lineElement.textStyle.isStrikeThrough()) { + worldRenderer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION); + if (!isShadow) lineElement.textStyle.getStrikeThroughShader().useShader(); + worldRenderer.pos(startX, y + lineElement.textStyle.getSize()/2, 0); + worldRenderer.pos(x, y + lineElement.textStyle.getSize()/2, 0); + worldRenderer.pos(x, y + lineElement.textStyle.getSize()/2+1, 0); + worldRenderer.pos(startX, y + lineElement.textStyle.getSize()/2+1, 0); + if (!isShadow) lineElement.textStyle.getStrikeThroughShader().freeShader(); + draw(worldRenderer); + } + if (lineElement.textStyle.isUnderline()) { + worldRenderer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION); + if (!isShadow) lineElement.textStyle.getUnderlineShader().useShader(); + worldRenderer.pos(startX, y + lineElement.textStyle.getSize(), 0); + worldRenderer.pos(x, y + lineElement.textStyle.getSize(), 0); + worldRenderer.pos(x, y + lineElement.textStyle.getSize()+1, 0); + worldRenderer.pos(startX, y + lineElement.textStyle.getSize()+1, 0); + if (!isShadow) lineElement.textStyle.getUnderlineShader().freeShader(); + draw(worldRenderer); + } + + if (isShadow) lineElement.textStyle.getShadowShader().freeShader(); + + } + + @Override + public void render(FlatTextSpan lineElement, double x, double y, double currentScale) { + WorldRenderer worldRenderer = Tessellator.getInstance().getWorldRenderer(); + GlStateManager.disableTexture2D(); + if (lineElement.textStyle.getBackgroundShader() != null) { + lineElement.textStyle.getBackgroundShader().useShader(); + worldRenderer.pos(x, y, 0); + worldRenderer.pos(x + lineElement.getWidth(), y, 0); + worldRenderer.pos(x + lineElement.getWidth(), y + lineElement.textStyle.getSize()+1, 0); + worldRenderer.pos(x, y + lineElement.textStyle.getSize()+1, 0); + lineElement.textStyle.getBackgroundShader().freeShader(); + draw(worldRenderer); + } + + if (lineElement.textStyle.isShadow()) + render(worldRenderer, lineElement, x+1,y+1, true); + render(worldRenderer, lineElement, x,y, false); + } + + + + private double renderChar(WorldRenderer worldRenderer, double x, double y, char ch, TextStyle textStyle) { + if (ch == ' ') { + return 4.0F; + } else { + int i = "ÀÁÂÈÊËÍÓÔÕÚßãõğİıŒœŞşŴŵžȇ\u0000\u0000\u0000\u0000\u0000\u0000\u0000 !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u0000ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜø£Ø׃áíóúñѪº¿®¬½¼¡«»░▒▓│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀αβΓπΣσμτΦΘΩδ∞∅∈∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■\u0000".indexOf(ch); + return i != -1 ? this.renderDefaultChar(worldRenderer, x, y, i, textStyle) : this.renderUnicodeChar(worldRenderer, x, y, ch, textStyle); + } + } + + + private void draw(WorldRenderer renderer) { + renderer.finishDrawing(); + if (renderer.getVertexCount() > 0) { + VertexFormat vertexformat = renderer.getVertexFormat(); + int i = vertexformat.getNextOffset(); + ByteBuffer bytebuffer = renderer.getByteBuffer(); + List list = vertexformat.getElements(); + + int i1; + for(i1 = 0; i1 < list.size(); ++i1) { + VertexFormatElement vertexformatelement = (VertexFormatElement)list.get(i1); + vertexformatelement.getUsage().preDraw(vertexformat, i1, i, bytebuffer); + } + + GL11.glDrawArrays(renderer.getDrawMode(), 0, renderer.getVertexCount()); + i1 = 0; + + for(int j1 = list.size(); i1 < j1; ++i1) { + VertexFormatElement vertexformatelement1 = (VertexFormatElement)list.get(i1); + vertexformatelement1.getUsage().postDraw(vertexformat, i1, i, bytebuffer); + } + } + + renderer.reset(); + } + + public void bindTexture(WorldRenderer worldRenderer, ResourceLocation resourceLocation) { + draw(worldRenderer); + Minecraft.getMinecraft().getTextureManager().bindTexture(resourceLocation); + } + + private double renderDefaultChar(WorldRenderer worldRenderer, double posX, double posY, int ch, TextStyle textStyle) { + int texX = ch % 16 * 8; + int texY = ch / 16 * 8; + int italicsAddition = textStyle.italics ? 1 : 0; + bindTexture(worldRenderer, this.locationFontTexture); + double charWidth = (this.charWidth[ch] - 1.0F) * textStyle.getSize() / 8.0; + double charHeight = textStyle.getSize(); + // char width contains the gap between next char + + + worldRenderer.pos(posX + (float)italicsAddition, posY, 0.0F).tex((float)texX / 128.0F, (float)texY / 128.0F); + worldRenderer.pos(posX - (float)italicsAddition, posY + charHeight - 0.01F, 0.0F).tex((float)texX / 128.0F, ((float)texY + 7.99F) / 128.0F); + worldRenderer.pos(posX + charWidth+ (float)italicsAddition - 0.01F , posY, 0.0F).tex(((float)texX + charWidth - 1.01F) / 128.0F, (float)texY / 128.0F); + worldRenderer.pos(posX + charWidth - (float)italicsAddition - 0.01F, posY + charHeight - 0.01F, 0.0F).tex(((float)texX + charWidth - 1.01F) / 128.0F, ((float)texY + 7.99F) / 128.0F); + + return charWidth + textStyle.getSize() / 8.0; + } + private double renderUnicodeChar(WorldRenderer worldRenderer, double posX, double posY, char ch, TextStyle textStyle) { + if (this.glyphData[ch] == 0) { + return 0.0F; + } else { + int i = ch / 256; + bindTexture(worldRenderer, this.getUnicodePageLocation(i)); + float xStart = (float)(this.glyphData[ch] >>> 4); + float xEnd = (float)(this.glyphData[ch] & 15 + 1); + + float texX = (float)(ch % 16 * 16) + xStart; + float texY = (float)((ch & 255) / 16 * 16); + float texWidth = xEnd - xStart - 0.02F; + float italicSlope = textStyle.italics ? 1.0F : 0.0F; + + double charWidth = texWidth * textStyle.getSize() / 16.0; + double charHeight = textStyle.getSize(); + + worldRenderer.pos(posX + italicSlope, posY, 0.0F) + .tex(texX / 256.0F, texY / 256.0F); + worldRenderer.pos(posX - italicSlope, posY + charHeight - 0.01F, 0.0F) + .tex(texX / 256.0F, (texY + 15.98F) / 256.0F); + worldRenderer.pos(posX + charWidth + italicSlope, posY, 0.0F) + .tex((texX + texWidth) / 256.0F, texY / 256.0F); + worldRenderer.pos(posX + charWidth - italicSlope, posY + charHeight - 0.01F, 0.0F) + .tex((texX + texWidth) / 256.0F, (texY + 15.98F) / 256.0F); + return charWidth + textStyle.getSize() / 8.0; + } + } +} diff --git a/mod/src/main/java/kr/syeyoung/dungeonsguide/mod/guiv2/elements/richtext/fonts/FontRenderer.java b/mod/src/main/java/kr/syeyoung/dungeonsguide/mod/guiv2/elements/richtext/fonts/FontRenderer.java new file mode 100644 index 00000000..b9f52c06 --- /dev/null +++ b/mod/src/main/java/kr/syeyoung/dungeonsguide/mod/guiv2/elements/richtext/fonts/FontRenderer.java @@ -0,0 +1,30 @@ +/* + * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod + * Copyright (C) 2023 cyoung06 (syeyoung) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package kr.syeyoung.dungeonsguide.mod.guiv2.elements.richtext.fonts; + +import kr.syeyoung.dungeonsguide.mod.guiv2.elements.richtext.FlatTextSpan; +import kr.syeyoung.dungeonsguide.mod.guiv2.elements.richtext.TextStyle; + +public interface FontRenderer { + double getWidth(char text, TextStyle textStyle); + // from y 0 going downward + double getBaselineHeight(TextStyle textStyle); + + void render(FlatTextSpan lineElement, double x, double v, double currentScale); +} diff --git a/mod/src/main/java/kr/syeyoung/dungeonsguide/mod/guiv2/elements/richtext/shaders/Shader.java b/mod/src/main/java/kr/syeyoung/dungeonsguide/mod/guiv2/elements/richtext/shaders/Shader.java new file mode 100644 index 00000000..4f14562d --- /dev/null +++ b/mod/src/main/java/kr/syeyoung/dungeonsguide/mod/guiv2/elements/richtext/shaders/Shader.java @@ -0,0 +1,24 @@ +/* + * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod + * Copyright (C) 2023 cyoung06 (syeyoung) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package kr.syeyoung.dungeonsguide.mod.guiv2.elements.richtext.shaders; + +public interface Shader { + public void useShader(); + public void freeShader(); +} diff --git a/mod/src/main/java/kr/syeyoung/dungeonsguide/mod/guiv2/elements/richtext/shaders/SingleColorShader.java b/mod/src/main/java/kr/syeyoung/dungeonsguide/mod/guiv2/elements/richtext/shaders/SingleColorShader.java new file mode 100644 index 00000000..bd686968 --- /dev/null +++ b/mod/src/main/java/kr/syeyoung/dungeonsguide/mod/guiv2/elements/richtext/shaders/SingleColorShader.java @@ -0,0 +1,40 @@ +/* + * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod + * Copyright (C) 2023 cyoung06 (syeyoung) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package kr.syeyoung.dungeonsguide.mod.guiv2.elements.richtext.shaders; + +import net.minecraft.client.renderer.GlStateManager; + +public class SingleColorShader implements Shader{ + private float r, g, b, a; + + // argb + public SingleColorShader(int color) { + r = ((color >> 16) & 0xFF) / 255.0f; + g = ((color >> 8) & 0xFF) / 255.0f; + b = ((color) & 0xFF) / 255.0f; + r = ((color >> 24) & 0xFF) / 255.0f; + } + @Override + public void useShader() { + GlStateManager.color(r,g,b,a); + } + + @Override + public void freeShader() {} +} -- cgit