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.
- Chat color model -
ChatColor.Legacyfor vanilla 1.8.9-style codes (0-9,a-f) andChatColor.Customfor arbitrary modern RGB ("color": "#FF00FF") with automatic shadow derivation via(rgb & 0xFCFCFC) >> 2 - Formatting codes -
ChatFormatenum coveringOBFUSCATED,BOLD,STRIKETHROUGH,UNDERLINE,ITALIC,RESET, plus theSECTION_SYMBOLconstant and legacy/alternate-code translation helpers - Segment primitives -
TextSegment(styled text run plus click/hover events and appended siblings) andLineSegment(list of styled runs) with builders and legacy-string parsing - Event payloads -
ClickEventandHoverEventwith GsonJsonObject(de)serialization - Native OTF rendering -
MinecraftFontloads the official Minecraft fonts (Regular, Bold, Italic, BoldItalic, Galactic, Illageralt) and rasterizes glyphs intoPixelBuffers with zero AWT overhead after initialization - Runtime font bootstrap - downstream consumers don't need to ship OTFs:
MinecraftFontinvokes the generator on first use when the classpath has nofonts/resources - Gson-backed JSON - segment, color, and event types round-trip through
JsonObject
| 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.
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 buildBuild 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.
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.
On first use, MinecraftFont resolves each OTF in priority order:
- Classpath -
fonts/Minecraft-*.otfviaClassLoader.getResourceAsStream - Module cache -
cache/fonts/Minecraft-*.otf - On-demand generation - invokes
ToolingFontsto clone the generator and produce the OTFs intocache/fonts/(version driven byMinecraftFont.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.
Run the fonts Gradle task to populate cache/fonts/:
./gradlew fonts # defaults to Minecraft 26.1
./gradlew fonts -PfontVersion=26.2 # pin a different versionThe 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 buildNote
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 jarYou 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.
| 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 |
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).
Each MinecraftFont enum value wraps a pre-loaded AWT Font and a lazy glyph atlas:
- 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 - Lazy rasterization - non-ASCII codepoints are rasterized on first access via
ConcurrentMap#computeIfAbsentand cached for subsequent renders - Draw-time tint - glyphs are stored as white-on-transparent
PixelBuffers and laid at draw time in the colour, its alpha included, so noGraphics2Dis 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 throughMinecraftGraphics#fillEffect, which takes mcPixels asdrawStringdoes
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.
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
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) |
See CONTRIBUTING.md for development setup, code style conventions, and how to submit a pull request.
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.