Skip to content
minecraft-libraryPublic

About

Models Minecraft's chat text system - colours, formatting codes, segment trees, click/hover event payloads - and renders it to raw pixel buffers via the vanilla OTF font family.

Topics

Resources

Code of conduct

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Minecraft Text

A Java 21 library that models Minecraft's chat text system - colours, formatting codes, segment trees, click/hover event payloads - and renders it to raw pixel buffers via the vanilla OTF font family.

Important

This project depends on font files derived from copyrighted bitmap assets owned by Mojang AB (a Microsoft subsidiary). The OTF files are never committed to this repository. They are generated on demand by the bundled fonts Gradle task, which invokes minecraft-library/font-generator against the official Minecraft client JAR. You are responsible for ensuring your use of the generated font files complies with the Minecraft EULA and Minecraft Usage Guidelines.

Table of Contents

Features

  • Chat color model - ChatColor.Legacy for vanilla 1.8.9-style codes (0-9, a-f) and ChatColor.Custom for arbitrary modern RGB ("color": "#FF00FF") with automatic shadow derivation via (rgb & 0xFCFCFC) >> 2
  • Formatting codes - ChatFormat enum covering OBFUSCATED, BOLD, STRIKETHROUGH, UNDERLINE, ITALIC, RESET, plus the SECTION_SYMBOL constant and legacy/alternate-code translation helpers
  • Segment primitives - TextSegment (styled text run plus click/hover events and appended siblings) and LineSegment (list of styled runs) with builders and legacy-string parsing
  • Event payloads - ClickEvent and HoverEvent with Gson JsonObject (de)serialization
  • Native OTF rendering - MinecraftFont loads the official Minecraft fonts (Regular, Bold, Italic, BoldItalic, Galactic, Illageralt) and rasterizes glyphs into PixelBuffers with zero AWT overhead after initialization
  • Runtime font bootstrap - downstream consumers don't need to ship OTFs: MinecraftFont invokes the generator on first use when the classpath has no fonts/ resources
  • Gson-backed JSON - segment, color, and event types round-trip through JsonObject

Getting Started

Prerequisites

Requirement Version Notes
JDK 21+ Required - the build toolchain targets Java 21
Git 2.x+ Required for cloning and for the fonts task
Python 3.10+ Required only when running the fonts task or the runtime bootstrap

Note

Python is not required to consume this library as a dependency if the upstream artifact already bundles the generated OTFs on its classpath. It is required only when this repository has to produce fresh font files.

Installation

Add the Gradle coordinates via JitPack:

repositories {
    mavenCentral()
    maven(url = "https://jitpack.io")
}

dependencies {
    implementation("com.github.minecraft-library:minecraft-text:<commit-or-tag>")
}

Or clone the repository and build locally:

git clone https://github.com/minecraft-library/minecraft-text.git
cd minecraft-text
./gradlew build

Usage

Build a single styled run and serialize it to JSON:

import lib.minecraft.text.ChatColor;
import lib.minecraft.text.TextSegment;

TextSegment hello = new TextSegment.Builder()
    .withText("Hello, world!")
    .withColor(ChatColor.Legacy.GOLD)
    .isBold()
    .build();

String json = hello.toJson().toString();

Compose multiple styled runs into a single line:

import lib.minecraft.text.LineSegment;
import lib.minecraft.text.TextSegment;

LineSegment line = LineSegment.builder()
    .segments(
        TextSegment.builder()
            .text("Hello, ")
            .color(ChatColor.Legacy.GOLD)
            .build(),
        TextSegment.builder()
            .text("world!")
            .color(ChatColor.of(0xFF00FF))
            .bold(true)
            .build()
    )
    .build();

Or parse a legacy section-symbol string directly:

// Accepts § natively and '&' as a substitute (second overload lets you pick another substitute)
LineSegment parsed = TextSegment.fromLegacy("&6Hello, &lworld!");

Render a line to a PixelBuffer:

import dev.simplified.image.PixelBuffer;
import lib.minecraft.text.LineSegment;
import lib.minecraft.text.TextSegment;
import lib.minecraft.text.font.MinecraftFont;
import lib.minecraft.text.font.MinecraftGraphics;

LineSegment line = TextSegment.fromLegacy("&6Hello, &lworld!");

// Output buffer is sized in native render pixels (mcPixels * MC_PIXEL_SCALE).
PixelBuffer buffer = PixelBuffer.create(256, 32);
MinecraftGraphics gfx = new MinecraftGraphics(buffer);

// drawString takes mcPixel coordinates; the baseline convention matches AWT. The pen is a float,
// as the client's is: a run's width can end on half an mcPixel, and the next run starts there.
float cursorXMcPx = 0;
int baselineYMcPx = MinecraftFont.Vanilla.REGULAR.metrics().getAscentMcPixels();

for (TextSegment segment : line.getSegments()) {
    String text = segment.getText();
    MinecraftFont font = segment.resolveFont();
    segment.getColor().ifPresent(c -> gfx.setColor(c.color()));
    gfx.setFont(font);
    gfx.drawString(text, cursorXMcPx, baselineYMcPx);
    cursorXMcPx += font.metrics().stringAdvanceMcPixels(text);
}

Tip

Call MinecraftFont.Vanilla.REGULAR.glyph(codepoint) directly if you need per-glyph bitmaps (e.g. for a custom layout engine). The glyph cache is thread-safe and lazily populated via ConcurrentMap#computeIfAbsent.

Fonts

Choosing a Run's Font

A run is drawn in the font its segment names, or in minecraft:default (FontId.DEFAULT) where it names none, in its bold and italic. TextSegment#resolveFont answers that font through MinecraftFont.of(FontId, Style), which answers a font for every id, as the client's font manager does:

Font id Font
minecraft:default the default font's member for the style - Vanilla.REGULAR, BOLD, ITALIC or BOLD_ITALIC - or a font registered under that member's id in its place
minecraft:alt, minecraft:illageralt Vanilla.GALACTIC, Vanilla.ILLAGERALT, or a font registered under the id in its place, in its one face whatever the style
a registered pack font's id that font, in its one face whatever the style
any other id MinecraftFont.Missing, the client's missing font

The missing font draws the client's missing glyph for every codepoint, U+0020 included: a hollow box 5 mcPixels wide and 8 tall, its top on the ascent line, moving the pen 6 mcPixels. A run naming an id the client has no font under draws it, as the client does. So does a run naming a font the client has that the library cannot draw - minecraft:uniform, minecraft:include/default, minecraft:include/space and minecraft:include/unifont - where the client draws that font's glyphs, so the boxes show plainly what the library cannot draw. The ids the library gives the default font's members (minecraft:default/bold, minecraft:default/italic, minecraft:default/bold_italic) name no font of the client's and draw it too; MinecraftFont.get answers each of them as it does any id - its member, or a font registered under it.

Runtime Bootstrap

On first use, MinecraftFont resolves each OTF in priority order:

  1. Classpath - fonts/Minecraft-*.otf via ClassLoader.getResourceAsStream
  2. Module cache - cache/fonts/Minecraft-*.otf
  3. On-demand generation - invokes ToolingFonts to clone the generator and produce the OTFs into cache/fonts/ (version driven by MinecraftFont.DEFAULT_VERSION)

This means downstream consumers can depend on minecraft-text without shipping any font assets - the first MinecraftFont.Vanilla.REGULAR.glyph(...) call transparently produces them.

Important

The runtime bootstrap requires git and Python 3.10+ on the host's PATH. In sealed environments (e.g. production Docker images) you should run the build-time generation during image build and ship the OTFs on the classpath instead.

Build-Time Generation

Run the fonts Gradle task to populate cache/fonts/:

./gradlew fonts                      # defaults to Minecraft 26.1
./gradlew fonts -PfontVersion=26.2   # pin a different version

The task clones minecraft-library/font-generator into cache/font-generator/, creates a Python virtual environment, installs the generator, and emits .otf files to cache/fonts/. Subsequent runs reuse the clone and venv so only the generator itself re-executes.

The standard processResources task then copies cache/fonts/ onto the runtime classpath under fonts/:

./gradlew build

Note

cache/ and src/main/resources/fonts/ are both gitignored. The fonts task writes exclusively into cache/fonts/; the source tree is never modified.

Build order for a fresh checkout
./gradlew fonts     # emit cache/fonts/Minecraft-*.otf (run once per version bump)
./gradlew build     # compile, test, and package the jar

You can skip the first step if you are only working on code paths that do not exercise MinecraftFont. Unit tests under MinecraftFontTest will fail without OTFs present.

Packages

Package Purpose
lib.minecraft.text Top-level model - ChatColor, ChatFormat, TextSegment, LineSegment
lib.minecraft.text.event ClickEvent, HoverEvent payloads with Gson (de)serialization
lib.minecraft.text.font MinecraftFont, MinecraftFontMetrics, MinecraftGraphics - OTF loading and glyph rendering
lib.minecraft.text.tooling ToolingFonts - Gradle/runtime-invoked wrapper around the Python font generator

How It Works

Segment Model

A Minecraft chat line is modelled as a list of styled text runs. Two classes cover the surface area:

TextSegment                                 # styled text run + interaction events + siblings
  ├── contents     (TextContents)
  ├── color        (Optional<ChatColor>)
  ├── bold / italic / underlined / obfuscated / strikethrough (Optional<Boolean>)
  ├── clickEvent   (Optional<ClickEvent>)
  ├── hoverEvent   (Optional<HoverEvent>)
  └── extra        (List<TextSegment>)

LineSegment                                 # an ordered list of runs
  └── segments     (ConcurrentList<TextSegment>)

TextSegment.fromLegacy(String) and LineSegment.fromLegacy(String, char) parse legacy section-symbol strings (§6...§l... or a user-chosen substitute) into the model, and TextSegment#toLines flattens a segment and its siblings into lines of runs. TextSegment#toJson emits the wire-format JsonElement; TextSegment.fromJson round-trips it back. A caller draws each resulting run through MinecraftGraphics#setFont(MinecraftFont) in the font TextSegment#resolveFont answers for it (see Choosing a Run's Font).

Glyph Rendering

Each MinecraftFont enum value wraps a pre-loaded AWT Font and a lazy glyph atlas:

  1. Enum init - the OTF resolves via the runtime bootstrap, AWT loads it at FONT_POINT_SIZE = 16f, font-level metrics (ascent, descent, height) are captured, and printable ASCII (U+0020 - U+007E) is eagerly rasterized so the first render has zero AWT overhead
  2. Lazy rasterization - non-ASCII codepoints are rasterized on first access via ConcurrentMap#computeIfAbsent and cached for subsequent renders
  3. Draw-time tint - glyphs are stored as white-on-transparent PixelBuffers and laid at draw time in the colour, its alpha included, so no Graphics2D is needed after initialization. A pixel's alpha is its texel's times the colour's; a pixel whose alpha comes to 25 or less (MinecraftGraphics.DISCARD_ALPHA) is not laid, as the client's text shader discards below 0.1, and the rest blend over the canvas as the client's translucent text does. A colour (sbix) glyph keeps its own pixels, the colour reaching none of them. A strikethrough or underline bar is laid the same way through MinecraftGraphics#fillEffect, which takes mcPixels as drawString does

The OTFs use unitsPerEm = 1024 with 128 units = 1 mcPixel, and MC_PIXEL_SCALE = 2 maps vanilla Minecraft pixels to output pixels at the standard load size.

Project Structure

minecraft-text/
├── src/
│   ├── main/java/lib/minecraft/text/
│   │   ├── ChatColor.java           # Legacy enum + Custom record
│   │   ├── ChatFormat.java          # Bold/italic/etc. enum
│   │   ├── TextSegment.java         # Recursive segment model
│   │   ├── LineSegment.java         # Flattened same-style runs
│   │   ├── event/
│   │   │   ├── ClickEvent.java
│   │   │   └── HoverEvent.java
│   │   ├── font/
│   │   │   ├── MinecraftFont.java          # OTF-backed glyph atlas enum
│   │   │   ├── MinecraftFontMetrics.java   # Ascent/descent/advance capture
│   │   │   └── MinecraftGraphics.java      # Line layout + PixelBuffer output
│   │   └── tooling/
│   │       └── ToolingFonts.java           # Gradle + runtime generator wrapper
│   └── test/java/lib/minecraft/renderer/text/
│       ├── ChatColorTest.java
│       └── font/MinecraftFontTest.java
├── build.gradle.kts
├── settings.gradle.kts
├── gradle/libs.versions.toml
├── LICENSE.md
├── COPYRIGHT.md
├── CONTRIBUTING.md
└── README.md

Runtime Directories

Created during execution and excluded from version control:

Directory Contents
cache/font-generator/ Clone of the Python font-generator repo and its virtual environment
cache/fonts/ Generated Minecraft-*.otf files - copied onto the classpath by processResources
build/ Gradle outputs (compiled classes, resources, jar)

Contributing

See CONTRIBUTING.md for development setup, code style conventions, and how to submit a pull request.

License

This project is licensed under the Apache License 2.0 - see LICENSE.md for the full text.

See COPYRIGHT.md for third-party attribution notices, including information about Mojang AB's copyrighted assets and the upstream font-generator tool.

About

Models Minecraft's chat text system - colours, formatting codes, segment trees, click/hover event payloads - and renders it to raw pixel buffers via the vanilla OTF font family.

Topics

Resources

Code of conduct

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Contributors

Languages