Кастомный шрифт в gui меню

Версия Minecraft
1.12.2
API
Forge
34
2
4
Сделаю первый шаг по решению этого вопроса за тебя
Спасибо, но я так уже делал, в основном там не решённые вопросы и далеко не в GUI, либо заменяется шрифт во всём майнкрафте либо только на табличке...
 

VeniVidiVici

Санта Барбарис
327
15
198
Считай, что у меня сегодня аукцион небывалой щедрости, а тебе просто повезло сегодня попасть под струю.

Код был в бородатые времена позаимствован с одного известного мода на 1.7, а потом адаптирован и под 1.12. Не подводил ещё. Шрифт в assets. Если кто ещё сочтёт это полезным, можно и в Сливы оформить, но предполагаю, что где-то на форуме подобное уже могли и выкладывать.

Вспомогательный класс №1
FontContainer:
public class FontContainer {
        private TrueTypeFont textFont = null;
        
        public boolean useCustomFont = true;
        
        private FontContainer() {}
        
        public FontContainer(String fontType, int fontSize) {
          this.textFont = new TrueTypeFont(new Font(fontType, 0, fontSize), 1.0F);
          this.useCustomFont = !fontType.equalsIgnoreCase("minecraft");
          try {
              System.out.println("[Fonts] Проверка директории со шрифтом.");
            if (!this.useCustomFont || fontType.isEmpty() || fontType.equalsIgnoreCase("default"))
              this.textFont = new TrueTypeFont(new ResourceLocation(YOUMODID, "YourFontInAssets.ttf"), fontSize, 1.0F);
          } catch (Exception e) {
              System.out.println("[Fonts] Шрифт не может быть загружен!");
              System.out.println(e.toString());
          }
        }
        
        public int height(String text) {
          if (this.useCustomFont)
            return this.textFont.height(text);
          return (Minecraft.getMinecraft()).fontRenderer.FONT_HEIGHT;
        }
        
        public int width(String text) {
          if (this.useCustomFont)
            return this.textFont.width(text);
          return (Minecraft.getMinecraft()).fontRenderer.getStringWidth(text);
        }
        
        public FontContainer copy() {
          FontContainer font = new FontContainer();
          font.textFont = this.textFont;
          font.useCustomFont = this.useCustomFont;
          return font;
        }
        
        public void drawString(String text, int x, int y, int color) {
          if (this.useCustomFont) {
            this.textFont.draw(text, x, y, color);
          } else {
            (Minecraft.getMinecraft()).fontRenderer.drawString(text, x, y, color);
          }
        }
        
        public String getName() {
          if (!this.useCustomFont)
            return "Minecraft";
          return this.textFont.getFontName();
        }
        
        public void clear() {
          if (this.textFont != null)
            this.textFont.dispose();
        }
}

Вспомогательный класс №2
TrueTypeFont:
public class TrueTypeFont {
  enum GlyphType {
      NORMAL, COLOR, RANDOM, BOLD, STRIKETHROUGH, UNDERLINE, ITALIC, RESET, OTHER;
  }
 
  private static final List<Font> allFonts = Arrays.asList(GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts());
 
  private List<Font> usedFonts = new ArrayList<>();
 
  @SuppressWarnings("unchecked")
  private LinkedHashMap<String, GlyphCache> textcache = (LinkedHashMap<String, GlyphCache>)new LRUHashMap(100);
 
  private Map<Character, Glyph> glyphcache = new HashMap<>();
 
  private List<TextureCache> textures = new ArrayList<>();
 
  private Font font;
 
  private int lineHeight = 1;
 
  private Graphics2D globalG = (Graphics2D)(new BufferedImage(1, 1, 2)).getGraphics();
 
  public float scale = 1.0F;
 
  private int specialChar = 167;
 
  public TrueTypeFont(Font font, float scale) {
    this.font = font;
    this.scale = scale;
    this.globalG.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    this.lineHeight = this.globalG.getFontMetrics(font).getHeight();
  }
 
  public TrueTypeFont(ResourceLocation resource, int fontSize, float scale) throws IOException, FontFormatException {
      InputStream stream = Minecraft.getMinecraft().getResourceManager().getResource(resource).getInputStream();
      Discord.InfoMessage("[Fonts] Путь загрузки шрифта корректен.");
      GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
      Font font = Font.createFont(0, stream);
      ge.registerFont(font);
      this.font = font.deriveFont(0, fontSize);
      this.scale = scale;
      this.globalG.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
      this.lineHeight = this.globalG.getFontMetrics(font).getHeight();
  }
 
  public void setSpecial(char c) {
    this.specialChar = c;
  }
 
  public void draw(String text, float x, float y, int color) {
    GlyphCache cache = getOrCreateCache(text);
    float r = (color >> 16 & 0xFF) / 255.0F;
    float g = (color >> 8 & 0xFF) / 255.0F;
    float b = (color & 0xFF) / 255.0F;
    GlStateManager.color(r, g, b, 1.0F);
    GlStateManager.enableBlend();
    GlStateManager.pushMatrix();
    GlStateManager.translate(x, y, 0.0F);
    GlStateManager.scale(this.scale, this.scale, 1.0F);
    float i = 0.0F;
    for (Glyph gl : cache.glyphs) {
      if (gl.type != GlyphType.NORMAL) {
        if (gl.type == GlyphType.RESET) {
          GlStateManager.color(r, g, b, 1.0F);
          continue;
        }
        if (gl.type == GlyphType.COLOR)
          GlStateManager.color((gl.color >> 16 & 0xFF) / 255.0F, (gl.color >> 8 & 0xFF) / 255.0F, (gl.color & 0xFF) / 255.0F, 1.0F);
        continue;
      }
      GlStateManager.bindTexture(gl.texture);
      drawTexturedModalRect(i, 0.0F, gl.x * textureScale(), gl.y * textureScale(), gl.width * textureScale(), gl.height * textureScale());
      i += gl.width * textureScale();
    }
    GlStateManager.disableBlend();
    GlStateManager.popMatrix();
    GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F);
  }
 
  private GlyphCache getOrCreateCache(String text) {
    GlyphCache cache = this.textcache.get(text);
    if (cache != null)
      return cache;
    cache = new GlyphCache();
    for (int i = 0; i < text.length(); i++) {
      char c = text.charAt(i);
      if (c == this.specialChar && i + 1 < text.length()) {
        char next = text.toLowerCase(Locale.ENGLISH).charAt(i + 1);
        int index = "0123456789abcdefklmnor".indexOf(next);
        if (index >= 0) {
          Glyph glyph = new Glyph();
          if (index < 16) {
            glyph.type = GlyphType.COLOR;
            glyph.color = (Minecraft.getMinecraft()).fontRenderer.getColorCode(next);
          } else if (index == 16) {
            glyph.type = GlyphType.RANDOM;
          } else if (index == 17) {
            glyph.type = GlyphType.BOLD;
          } else if (index == 18) {
            glyph.type = GlyphType.STRIKETHROUGH;
          } else if (index == 19) {
            glyph.type = GlyphType.UNDERLINE;
          } else if (index == 20) {
            glyph.type = GlyphType.ITALIC;
          } else {
            glyph.type = GlyphType.RESET;
          }
          cache.glyphs.add(glyph);
          i++;
          continue;
        }
      }
      Glyph g = getOrCreateGlyph(c);
      cache.glyphs.add(g);
      cache.width += g.width;
      cache.height = Math.max(cache.height, g.height);
      continue;
    }
    this.textcache.put(text, cache);
    return cache;
  }
 
  private Glyph getOrCreateGlyph(char c) {
    Glyph g = this.glyphcache.get(Character.valueOf(c));
    if (g != null)
      return g;
    TextureCache cache = getCurrentTexture();
    Font font = getFontForChar(c);
    FontMetrics metrics = this.globalG.getFontMetrics(font);
    g = new Glyph();
    g.width = Math.max(metrics.charWidth(c), 1);
    g.height = Math.max(metrics.getHeight(), 1);
    if (cache.x + g.width >= 512) {
      cache.x = 0;
      cache.y += this.lineHeight + 1;
      if (cache.y >= 512) {
        cache.full = true;
        cache = getCurrentTexture();
      }
    }
    g.x = cache.x;
    g.y = cache.y;
    cache.x += g.width + 3;
    this.lineHeight = Math.max(this.lineHeight, g.height);
    cache.g.setFont(font);
    cache.g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    cache.g.drawString(c + "", g.x, g.y + metrics.getAscent());
    g.texture = cache.textureId;
    TextureUtil.uploadTextureImage(cache.textureId, cache.bufferedImage);
    this.glyphcache.put(Character.valueOf(c), g);
    return g;
  }
 
  private TextureCache getCurrentTexture() {
    TextureCache cache = null;
    for (TextureCache t : this.textures) {
      if (!t.full) {
        cache = t;
        break;
      }
    }
    if (cache == null)
      this.textures.add(cache = new TextureCache());
    return cache;
  }
 
  public void drawCentered(String text, float x, float y, int color) {
    draw(text, x - width(text) / 2.0F, y, color);
  }
 
  private Font getFontForChar(char c) {
    if (this.font.canDisplay(c))
      return this.font;
    for (Font f : this.usedFonts) {
      if (f.canDisplay(c))
        return f;
    }
    Font fa = new Font("Arial Unicode MS", 0, this.font.getSize());
    if (fa.canDisplay(c))
      return fa;
    for (Font f : allFonts) {
      if (f.canDisplay(c)) {
        this.usedFonts.add(f = f.deriveFont(0, this.font.getSize()));
        return f;
      }
    }
    return this.font;
  }
 
  public void drawTexturedModalRect(float x, float y, float textureX, float textureY, float width, float height) {
    float f = 0.00390625F;
    float f1 = 0.00390625F;
    int zLevel = 0;
    BufferBuilder tessellator = Tessellator.getInstance().getBuffer();
    tessellator.begin(7, DefaultVertexFormats.POSITION_TEX);
    tessellator.noColor();
    tessellator.pos(x, (y + height), zLevel).tex((textureX * f), ((textureY + height) * f1)).endVertex();
    tessellator.pos((x + width), (y + height), zLevel).tex(((textureX + width) * f), ((textureY + height) * f1)).endVertex();
    tessellator.pos((x + width), y, zLevel).tex(((textureX + width) * f), (textureY * f1)).endVertex();
    tessellator.pos(x, y, zLevel).tex((textureX * f), (textureY * f1)).endVertex();
    Tessellator.getInstance().draw();
  }
 
  public int width(String text) {
    GlyphCache cache = getOrCreateCache(text);
    return (int)(cache.width * this.scale * textureScale());
  }
 
  public int height(String text) {
    if (text == null || text.trim().isEmpty())
      return (int)(this.lineHeight * this.scale * textureScale());
    GlyphCache cache = getOrCreateCache(text);
    return Math.max(1, (int)(cache.height * this.scale * textureScale()));
  }
 
  private float textureScale() {
    return 0.5F;
  }
 
  public void dispose() {
    for (TextureCache cache : this.textures)
      GlStateManager.bindTexture(cache.textureId);
    this.textcache.clear();
  }
 
  class TextureCache {
    int x;
    
    int y;
    
    int textureId = GlStateManager.generateTexture();
    
    BufferedImage bufferedImage = new BufferedImage(512, 512, 2);
    
    Graphics2D g = (Graphics2D)this.bufferedImage.getGraphics();
    
    boolean full;
  }
 
  class Glyph {
    TrueTypeFont.GlyphType type = TrueTypeFont.GlyphType.NORMAL;
    
    int color = -1;
    
    int x;
    
    int y;
    
    int height;
    
    int width;
    
    int texture;
  }
 
  class GlyphCache {
    public int width;
    
    public int height;
    
    List<TrueTypeFont.Glyph> glyphs = new ArrayList<>();
  }
 
  public String getFontName() {
    return this.font.getFontName();
  }
}

Класс №3 (Все три в отдельную папку складывай, для удобства)
LRUHashMap:
public class LRUHashMap<K, V> extends LinkedHashMap<K, V> {
    private final int maxSize;

    public LRUHashMap(int size) {
        super(size, 0.75F, true);
        this.maxSize = size;
    }

    protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
        return (size() > this.maxSize);
    }
}

В своём главном классе созадём статики:
MainClass:
public static String FontType = "Default";
public static int FontSize = 18;

В клиентской части сам контейнер шрифта и его загрузчик:
ClientSide:
public class ClientSide extends ServerSide {
    
    public static FontContainer Font;
    
    @Override
    public void preInit(FMLPreInitializationEvent event) {
        super.preInit(event);
        Font = new FontContainer(Mainmod.FontType, Mainmod.FontSize);
        
    }

}

А потом в том же оверлее используешь:
ClientSide.Font.drawString(text, x, y, color);

И даже можно так вот ловить его ширину:
ClientSide.Font.width(text);
 
7,099
324
1,510
Какой смысл экономить дроуколы до тех пор, пока не лагает?
Сделать лучше можно почти всегда, но заметит ли юзер разницу?
Он же не полезет в сорцы: "ой, много дроуколов, не буду в это играть"

Я все еще не проверял, насколько сильно это снижает фпс, но чувак утверждает
Не подводил ещё
Он же не просто так это говорит, верно?
 

tox1cozZ

aka Agravaine
8,456
598
2,893
Отличная реализация шрифта в SmoothFonts, которая не только вносит красивый и качественный шрифт, а еще и оптимизирует его рендер.

Так же понравилась эта, забрал себе. Да, нужно заранее генерировать шрифт программой, но выглядит всё отлично и работает быстро.
 

VeniVidiVici

Санта Барбарис
327
15
198
Мм, куча дроуколлов) Бесполезный скейл, бесполезный транслейт))0 Сомнительно конечно
Ну я же не позиционирую это лучшим решением из всех возможных. Это чисто слив куска кода с древнего мода
 

Icosider

Kotliner
Администратор
3,603
99
664
Какой смысл экономить дроуколы до тех пор, пока не лагает?
Сделать лучше можно почти всегда, но заметит ли юзер разницу?
Он же не полезет в сорцы: "ой, много дроуколов, не буду в это играть"

Я все еще не проверял, насколько сильно это снижает фпс, но чувак утверждает

Он же не просто так это говорит, верно?
Да, нахер оптимизацию)) Давайте сразу на старых версиях openGL писать, а чо? Раньше ж было лучше, удобней, glVertex, glMatrix и т.п. Чем меньше дроуколов тем лучше, уже давно обсудили это на всяких gamedev форумах. Юзверь не заметит, а ещё юзверь не заметит как на каждый чих создаются новые вектора, постоянное создание новых объектов событий, запись в файл в мейн потоке и прочее, да мелочь, а потом сидишь и ахереваешь. Уж извините, я перебрал довольно много паблик модов и мне есть, что сказать. Да и юнитТесты тоже нахер делать, у меня не лагает, значит и у всех не будет лагать

"Он же не просто так это говорит, верно?", если никто не жалуется, это не значит, что проблемы нет

Ну я же не позиционирую это лучшим решением из всех возможных. Это чисто слив куска кода с древнего мода
Вопросов нет, я высказал своё мнение

Да, нужно заранее генерировать шрифт программой, но выглядит всё отлично и работает быстро.
Можно как в беттерФонтс генерировать, но лучше думаю будет иметь заготовленные, да может не так гибко, как с генерацией в рантайме, но майну больше то и не нужно
 
7,099
324
1,510
Да и юнитТесты тоже нахер делать, у меня не лагает, значит и у всех не будет лагать
При чем здесь это?
если никто не жалуется, это не значит, что проблемы нет
В каком-нить фпс-игровом-режиме, наверное, буду жаловаться. А в какой-нить сборке для распахивания модов максимальный фпс не нужен. А в каком-нить внутриигровом шопе вообще не важно, сколько фпс, потому что статичная гуишка, где сцена меняется только при кликах.
Требуемый фпс зависит от предметной области.
 

Icosider

Kotliner
Администратор
3,603
99
664
При чем здесь это?
Ты сказал про досрочную оптимизацию, я ответил

В каком-нить фпс-игровом-режиме
Хорошо, смотри. У него на каждый глиф делает +1 дроуколл, текст состоящий из 300 символов(не считая пробелы) это уже +300 дроуколлов. Учитывая что большинство игроков это динозавры, у которых не компы, а походу вёдра с болтами, то для них это будет довольно ощутимо, не забываем, что используется сборка с около 150+ модами, тот же АЕ2 кстати если запихнуть в один чанк, тоже вызовет не мало проблем(скорее всего дело в рендере, так как клиентская и серверная логика у него отделены). Сколько всего дроуколлов на солянке из 150+ модов, я не проверял, но уже доказано, что чем меньше дроуколлов, тем лучше. Статью я уже скидывал с +- более менее явным примером. Эксперты @Dahaka, @GloomyFolken может добавят что-то или объяснят более подробно, почему меньше дроуколлов это хорошо
 
7,099
324
1,510
Ты сказал про досрочную оптимизацию, я ответил
Если тест проверяет время выполнения/потребление памяти, то можно установить порог прохождения ниже
У него на каждый глиф делает +1 дроуколл
Ладно, в данном случае действительно много
 
Считай, что у меня сегодня аукцион небывалой щедрости, а тебе просто повезло сегодня попасть под струю.

Код был в бородатые времена позаимствован с одного известного мода на 1.7, а потом адаптирован и под 1.12. Не подводил ещё. Шрифт в assets. Если кто ещё сочтёт это полезным, можно и в Сливы оформить, но предполагаю, что где-то на форуме подобное уже могли и выкладывать.

Вспомогательный класс №1
FontContainer:
public class FontContainer {
        private TrueTypeFont textFont = null;
       
        public boolean useCustomFont = true;
       
        private FontContainer() {}
       
        public FontContainer(String fontType, int fontSize) {
          this.textFont = new TrueTypeFont(new Font(fontType, 0, fontSize), 1.0F);
          this.useCustomFont = !fontType.equalsIgnoreCase("minecraft");
          try {
              System.out.println("[Fonts] Проверка директории со шрифтом.");
            if (!this.useCustomFont || fontType.isEmpty() || fontType.equalsIgnoreCase("default"))
              this.textFont = new TrueTypeFont(new ResourceLocation(YOUMODID, "YourFontInAssets.ttf"), fontSize, 1.0F);
          } catch (Exception e) {
              System.out.println("[Fonts] Шрифт не может быть загружен!");
              System.out.println(e.toString());
          }
        }
       
        public int height(String text) {
          if (this.useCustomFont)
            return this.textFont.height(text);
          return (Minecraft.getMinecraft()).fontRenderer.FONT_HEIGHT;
        }
       
        public int width(String text) {
          if (this.useCustomFont)
            return this.textFont.width(text);
          return (Minecraft.getMinecraft()).fontRenderer.getStringWidth(text);
        }
       
        public FontContainer copy() {
          FontContainer font = new FontContainer();
          font.textFont = this.textFont;
          font.useCustomFont = this.useCustomFont;
          return font;
        }
       
        public void drawString(String text, int x, int y, int color) {
          if (this.useCustomFont) {
            this.textFont.draw(text, x, y, color);
          } else {
            (Minecraft.getMinecraft()).fontRenderer.drawString(text, x, y, color);
          }
        }
       
        public String getName() {
          if (!this.useCustomFont)
            return "Minecraft";
          return this.textFont.getFontName();
        }
       
        public void clear() {
          if (this.textFont != null)
            this.textFont.dispose();
        }
}

Вспомогательный класс №2
TrueTypeFont:
public class TrueTypeFont {
  enum GlyphType {
      NORMAL, COLOR, RANDOM, BOLD, STRIKETHROUGH, UNDERLINE, ITALIC, RESET, OTHER;
  }
 
  private static final List<Font> allFonts = Arrays.asList(GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts());
 
  private List<Font> usedFonts = new ArrayList<>();
 
  @SuppressWarnings("unchecked")
  private LinkedHashMap<String, GlyphCache> textcache = (LinkedHashMap<String, GlyphCache>)new LRUHashMap(100);
 
  private Map<Character, Glyph> glyphcache = new HashMap<>();
 
  private List<TextureCache> textures = new ArrayList<>();
 
  private Font font;
 
  private int lineHeight = 1;
 
  private Graphics2D globalG = (Graphics2D)(new BufferedImage(1, 1, 2)).getGraphics();
 
  public float scale = 1.0F;
 
  private int specialChar = 167;
 
  public TrueTypeFont(Font font, float scale) {
    this.font = font;
    this.scale = scale;
    this.globalG.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    this.lineHeight = this.globalG.getFontMetrics(font).getHeight();
  }
 
  public TrueTypeFont(ResourceLocation resource, int fontSize, float scale) throws IOException, FontFormatException {
      InputStream stream = Minecraft.getMinecraft().getResourceManager().getResource(resource).getInputStream();
      Discord.InfoMessage("[Fonts] Путь загрузки шрифта корректен.");
      GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
      Font font = Font.createFont(0, stream);
      ge.registerFont(font);
      this.font = font.deriveFont(0, fontSize);
      this.scale = scale;
      this.globalG.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
      this.lineHeight = this.globalG.getFontMetrics(font).getHeight();
  }
 
  public void setSpecial(char c) {
    this.specialChar = c;
  }
 
  public void draw(String text, float x, float y, int color) {
    GlyphCache cache = getOrCreateCache(text);
    float r = (color >> 16 & 0xFF) / 255.0F;
    float g = (color >> 8 & 0xFF) / 255.0F;
    float b = (color & 0xFF) / 255.0F;
    GlStateManager.color(r, g, b, 1.0F);
    GlStateManager.enableBlend();
    GlStateManager.pushMatrix();
    GlStateManager.translate(x, y, 0.0F);
    GlStateManager.scale(this.scale, this.scale, 1.0F);
    float i = 0.0F;
    for (Glyph gl : cache.glyphs) {
      if (gl.type != GlyphType.NORMAL) {
        if (gl.type == GlyphType.RESET) {
          GlStateManager.color(r, g, b, 1.0F);
          continue;
        }
        if (gl.type == GlyphType.COLOR)
          GlStateManager.color((gl.color >> 16 & 0xFF) / 255.0F, (gl.color >> 8 & 0xFF) / 255.0F, (gl.color & 0xFF) / 255.0F, 1.0F);
        continue;
      }
      GlStateManager.bindTexture(gl.texture);
      drawTexturedModalRect(i, 0.0F, gl.x * textureScale(), gl.y * textureScale(), gl.width * textureScale(), gl.height * textureScale());
      i += gl.width * textureScale();
    }
    GlStateManager.disableBlend();
    GlStateManager.popMatrix();
    GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F);
  }
 
  private GlyphCache getOrCreateCache(String text) {
    GlyphCache cache = this.textcache.get(text);
    if (cache != null)
      return cache;
    cache = new GlyphCache();
    for (int i = 0; i < text.length(); i++) {
      char c = text.charAt(i);
      if (c == this.specialChar && i + 1 < text.length()) {
        char next = text.toLowerCase(Locale.ENGLISH).charAt(i + 1);
        int index = "0123456789abcdefklmnor".indexOf(next);
        if (index >= 0) {
          Glyph glyph = new Glyph();
          if (index < 16) {
            glyph.type = GlyphType.COLOR;
            glyph.color = (Minecraft.getMinecraft()).fontRenderer.getColorCode(next);
          } else if (index == 16) {
            glyph.type = GlyphType.RANDOM;
          } else if (index == 17) {
            glyph.type = GlyphType.BOLD;
          } else if (index == 18) {
            glyph.type = GlyphType.STRIKETHROUGH;
          } else if (index == 19) {
            glyph.type = GlyphType.UNDERLINE;
          } else if (index == 20) {
            glyph.type = GlyphType.ITALIC;
          } else {
            glyph.type = GlyphType.RESET;
          }
          cache.glyphs.add(glyph);
          i++;
          continue;
        }
      }
      Glyph g = getOrCreateGlyph(c);
      cache.glyphs.add(g);
      cache.width += g.width;
      cache.height = Math.max(cache.height, g.height);
      continue;
    }
    this.textcache.put(text, cache);
    return cache;
  }
 
  private Glyph getOrCreateGlyph(char c) {
    Glyph g = this.glyphcache.get(Character.valueOf(c));
    if (g != null)
      return g;
    TextureCache cache = getCurrentTexture();
    Font font = getFontForChar(c);
    FontMetrics metrics = this.globalG.getFontMetrics(font);
    g = new Glyph();
    g.width = Math.max(metrics.charWidth(c), 1);
    g.height = Math.max(metrics.getHeight(), 1);
    if (cache.x + g.width >= 512) {
      cache.x = 0;
      cache.y += this.lineHeight + 1;
      if (cache.y >= 512) {
        cache.full = true;
        cache = getCurrentTexture();
      }
    }
    g.x = cache.x;
    g.y = cache.y;
    cache.x += g.width + 3;
    this.lineHeight = Math.max(this.lineHeight, g.height);
    cache.g.setFont(font);
    cache.g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    cache.g.drawString(c + "", g.x, g.y + metrics.getAscent());
    g.texture = cache.textureId;
    TextureUtil.uploadTextureImage(cache.textureId, cache.bufferedImage);
    this.glyphcache.put(Character.valueOf(c), g);
    return g;
  }
 
  private TextureCache getCurrentTexture() {
    TextureCache cache = null;
    for (TextureCache t : this.textures) {
      if (!t.full) {
        cache = t;
        break;
      }
    }
    if (cache == null)
      this.textures.add(cache = new TextureCache());
    return cache;
  }
 
  public void drawCentered(String text, float x, float y, int color) {
    draw(text, x - width(text) / 2.0F, y, color);
  }
 
  private Font getFontForChar(char c) {
    if (this.font.canDisplay(c))
      return this.font;
    for (Font f : this.usedFonts) {
      if (f.canDisplay(c))
        return f;
    }
    Font fa = new Font("Arial Unicode MS", 0, this.font.getSize());
    if (fa.canDisplay(c))
      return fa;
    for (Font f : allFonts) {
      if (f.canDisplay(c)) {
        this.usedFonts.add(f = f.deriveFont(0, this.font.getSize()));
        return f;
      }
    }
    return this.font;
  }
 
  public void drawTexturedModalRect(float x, float y, float textureX, float textureY, float width, float height) {
    float f = 0.00390625F;
    float f1 = 0.00390625F;
    int zLevel = 0;
    BufferBuilder tessellator = Tessellator.getInstance().getBuffer();
    tessellator.begin(7, DefaultVertexFormats.POSITION_TEX);
    tessellator.noColor();
    tessellator.pos(x, (y + height), zLevel).tex((textureX * f), ((textureY + height) * f1)).endVertex();
    tessellator.pos((x + width), (y + height), zLevel).tex(((textureX + width) * f), ((textureY + height) * f1)).endVertex();
    tessellator.pos((x + width), y, zLevel).tex(((textureX + width) * f), (textureY * f1)).endVertex();
    tessellator.pos(x, y, zLevel).tex((textureX * f), (textureY * f1)).endVertex();
    Tessellator.getInstance().draw();
  }
 
  public int width(String text) {
    GlyphCache cache = getOrCreateCache(text);
    return (int)(cache.width * this.scale * textureScale());
  }
 
  public int height(String text) {
    if (text == null || text.trim().isEmpty())
      return (int)(this.lineHeight * this.scale * textureScale());
    GlyphCache cache = getOrCreateCache(text);
    return Math.max(1, (int)(cache.height * this.scale * textureScale()));
  }
 
  private float textureScale() {
    return 0.5F;
  }
 
  public void dispose() {
    for (TextureCache cache : this.textures)
      GlStateManager.bindTexture(cache.textureId);
    this.textcache.clear();
  }
 
  class TextureCache {
    int x;
   
    int y;
   
    int textureId = GlStateManager.generateTexture();
   
    BufferedImage bufferedImage = new BufferedImage(512, 512, 2);
   
    Graphics2D g = (Graphics2D)this.bufferedImage.getGraphics();
   
    boolean full;
  }
 
  class Glyph {
    TrueTypeFont.GlyphType type = TrueTypeFont.GlyphType.NORMAL;
   
    int color = -1;
   
    int x;
   
    int y;
   
    int height;
   
    int width;
   
    int texture;
  }
 
  class GlyphCache {
    public int width;
   
    public int height;
   
    List<TrueTypeFont.Glyph> glyphs = new ArrayList<>();
  }
 
  public String getFontName() {
    return this.font.getFontName();
  }
}

Класс №3 (Все три в отдельную папку складывай, для удобства)
LRUHashMap:
public class LRUHashMap<K, V> extends LinkedHashMap<K, V> {
    private final int maxSize;

    public LRUHashMap(int size) {
        super(size, 0.75F, true);
        this.maxSize = size;
    }

    protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
        return (size() > this.maxSize);
    }
}

В своём главном классе созадём статики:
MainClass:
public static String FontType = "Default";
public static int FontSize = 18;

В клиентской части сам контейнер шрифта и его загрузчик:
ClientSide:
public class ClientSide extends ServerSide {
   
    public static FontContainer Font;
   
    @Override
    public void preInit(FMLPreInitializationEvent event) {
        super.preInit(event);
        Font = new FontContainer(Mainmod.FontType, Mainmod.FontSize);
       
    }

}

А потом в том же оверлее используешь:
ClientSide.Font.drawString(text, x, y, color);

И даже можно так вот ловить его ширину:
ClientSide.Font.width(text);
Чувак
А ты можешь мне помочь с кое-чем? Мне необходимо добавить свой шрифт для гуи и интерфейса Custom NPC на 1.7.10 майнкрафте, помоги, буду признателен :3
 
Сверху