Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ modules.xml
logs/
.claude


# ProjectAtlas runtime state
/.projectatlas/*
!/.projectatlas/config.toml
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled
javaVersion=25
mcVersion=26.2
group=dev.slne.surf.api
version=3.42.0
version=3.43.0
relocationPrefix=dev.slne.surf.api.libs
snapshot=false
64 changes: 49 additions & 15 deletions surf-api-core/surf-api-core/api/surf-api-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -7233,6 +7233,33 @@ public final class dev/slne/surf/api/core/generated/VanillaAdvancementKeys$Story
public static final field UPGRADE_TOOLS Lnet/kyori/adventure/key/Key;
}

public final class dev/slne/surf/api/core/inventory/framework/internal/DefaultFontWidths {
public static final field BOLD_EXTRA_ADVANCE I
public static final field DEFAULT_ADVANCE I
public static final field INSTANCE Ldev/slne/surf/api/core/inventory/framework/internal/DefaultFontWidths;
public static final field SPACE_ADVANCE I
public final fun charWidth (IZLit/unimi/dsi/fastutil/ints/Int2IntMap;)I
public static synthetic fun charWidth$default (Ldev/slne/surf/api/core/inventory/framework/internal/DefaultFontWidths;IZLit/unimi/dsi/fastutil/ints/Int2IntMap;ILjava/lang/Object;)I
public final fun componentWidth (Lnet/kyori/adventure/text/Component;ZLit/unimi/dsi/fastutil/ints/Int2IntMap;)I
public static synthetic fun componentWidth$default (Ldev/slne/surf/api/core/inventory/framework/internal/DefaultFontWidths;Lnet/kyori/adventure/text/Component;ZLit/unimi/dsi/fastutil/ints/Int2IntMap;ILjava/lang/Object;)I
public final fun getAdvances ()Lit/unimi/dsi/fastutil/ints/Int2IntMap;
public final fun textWidth (Ljava/lang/String;ZLit/unimi/dsi/fastutil/ints/Int2IntMap;)I
public static synthetic fun textWidth$default (Ldev/slne/surf/api/core/inventory/framework/internal/DefaultFontWidths;Ljava/lang/String;ZLit/unimi/dsi/fastutil/ints/Int2IntMap;ILjava/lang/Object;)I
}

public final class dev/slne/surf/api/core/inventory/framework/internal/LaidOutViewText {
public fun <init> (Lnet/kyori/adventure/text/Component;I)V
public final fun component1 ()Lnet/kyori/adventure/text/Component;
public final fun component2 ()I
public final fun copy (Lnet/kyori/adventure/text/Component;I)Ldev/slne/surf/api/core/inventory/framework/internal/LaidOutViewText;
public static synthetic fun copy$default (Ldev/slne/surf/api/core/inventory/framework/internal/LaidOutViewText;Lnet/kyori/adventure/text/Component;IILjava/lang/Object;)Ldev/slne/surf/api/core/inventory/framework/internal/LaidOutViewText;
public fun equals (Ljava/lang/Object;)Z
public final fun getComponent ()Lnet/kyori/adventure/text/Component;
public final fun getWidth ()I
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}

public abstract class dev/slne/surf/api/core/inventory/framework/internal/PaginationButtonGlyphs : java/lang/Enum {
public static final field DISABLED Ldev/slne/surf/api/core/inventory/framework/internal/PaginationButtonGlyphs;
public static final field DISABLED_LEFT Ldev/slne/surf/api/core/inventory/framework/internal/PaginationButtonGlyphs;
Expand All @@ -7244,19 +7271,6 @@ public abstract class dev/slne/surf/api/core/inventory/framework/internal/Pagina
public static fun values ()[Ldev/slne/surf/api/core/inventory/framework/internal/PaginationButtonGlyphs;
}

public final class dev/slne/surf/api/core/inventory/framework/internal/RenderedViewTitle {
public fun <init> (Ljava/lang/String;I)V
public final fun component1 ()Ljava/lang/String;
public final fun component2 ()I
public final fun copy (Ljava/lang/String;I)Ldev/slne/surf/api/core/inventory/framework/internal/RenderedViewTitle;
public static synthetic fun copy$default (Ldev/slne/surf/api/core/inventory/framework/internal/RenderedViewTitle;Ljava/lang/String;IILjava/lang/Object;)Ldev/slne/surf/api/core/inventory/framework/internal/RenderedViewTitle;
public fun equals (Ljava/lang/Object;)Z
public final fun getText ()Ljava/lang/String;
public final fun getWidth ()I
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}

public final class dev/slne/surf/api/core/inventory/framework/internal/ShiftGlyphs {
public static final field INSTANCE Ldev/slne/surf/api/core/inventory/framework/internal/ShiftGlyphs;
public final fun renderShift (I)Ljava/lang/String;
Expand Down Expand Up @@ -7286,8 +7300,28 @@ public final class dev/slne/surf/api/core/inventory/framework/internal/ViewButto

public final class dev/slne/surf/api/core/inventory/framework/internal/ViewContainerRenderingKt {
public static final fun appendShiftedComponent (Ldev/slne/surf/api/core/messages/builder/SurfComponentBuilder;IILkotlin/jvm/functions/Function1;)V
public static final fun renderViewTitle (Ljava/lang/String;IIZLit/unimi/dsi/fastutil/ints/Int2IntMap;)Ldev/slne/surf/api/core/inventory/framework/internal/RenderedViewTitle;
public static synthetic fun renderViewTitle$default (Ljava/lang/String;IIZLit/unimi/dsi/fastutil/ints/Int2IntMap;ILjava/lang/Object;)Ldev/slne/surf/api/core/inventory/framework/internal/RenderedViewTitle;
}

public final class dev/slne/surf/api/core/inventory/framework/internal/ViewSlotGeometry {
public static final field COLUMNS I
public static final field INSTANCE Ldev/slne/surf/api/core/inventory/framework/internal/ViewSlotGeometry;
public static final field MAX_ROWS I
public static final field SLOT_SIZE I
public final fun columnOf (II)I
public static synthetic fun columnOf$default (Ldev/slne/surf/api/core/inventory/framework/internal/ViewSlotGeometry;IIILjava/lang/Object;)I
public final fun columnShift (III)I
public static synthetic fun columnShift$default (Ldev/slne/surf/api/core/inventory/framework/internal/ViewSlotGeometry;IIIILjava/lang/Object;)I
public final fun rowOf (II)I
public static synthetic fun rowOf$default (Ldev/slne/surf/api/core/inventory/framework/internal/ViewSlotGeometry;IIILjava/lang/Object;)I
public final fun slotOf (III)I
public static synthetic fun slotOf$default (Ldev/slne/surf/api/core/inventory/framework/internal/ViewSlotGeometry;IIIILjava/lang/Object;)I
public final fun spanWidth (II)I
public static synthetic fun spanWidth$default (Ldev/slne/surf/api/core/inventory/framework/internal/ViewSlotGeometry;IIILjava/lang/Object;)I
}

public final class dev/slne/surf/api/core/inventory/framework/internal/ViewTextLayoutKt {
public static final fun layoutViewText (Lnet/kyori/adventure/text/Component;IZLjava/lang/Integer;ZLkotlin/jvm/functions/Function2;)Ldev/slne/surf/api/core/inventory/framework/internal/LaidOutViewText;
public static synthetic fun layoutViewText$default (Lnet/kyori/adventure/text/Component;IZLjava/lang/Integer;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Ldev/slne/surf/api/core/inventory/framework/internal/LaidOutViewText;
}

public final class dev/slne/surf/api/core/invoker/HiddenInvokerUtil {
Expand Down
3 changes: 3 additions & 0 deletions surf-api-core/surf-api-core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ plugins {
dependencies {
api(projects.surfApiShared.surfApiSharedPublic)
api(libs.adventure.nbt)
// Exposed transitively by surf-api-shared-public as well; declared here so the test convention
// (testImplementation extendsFrom compileOnlyApi) puts Adventure on the test classpath.
compileOnlyApi(libs.adventure.api)
compileOnlyApi(libs.luckperms)
compileOnlyApi(libs.packetevents.api)
compileOnlyApi(libs.dazzleconf)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
package dev.slne.surf.api.core.inventory.framework.internal

import dev.slne.surf.api.core.util.freeze
import dev.slne.surf.api.shared.api.util.InternalSurfApi
import it.unimi.dsi.fastutil.ints.Int2IntMap
import it.unimi.dsi.fastutil.ints.Int2IntMaps
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap
import net.kyori.adventure.text.Component
import net.kyori.adventure.text.format.TextDecoration

/**
* Advance widths of the vanilla `minecraft:default` font, used to lay out header text that is
* rendered in the client's own font instead of a fixed-width resource-pack font.
*
* An *advance* is the number of pixels the render cursor moves on for one glyph — the glyph's own
* width plus the one pixel of spacing the font renderer inserts after it. Every code point not
* listed in [advances] advances by [DEFAULT_ADVANCE]; bold text adds [BOLD_EXTRA_ADVANCE] on top of
* every glyph.
*
* The table is measured off the three bitmap sheets of the vanilla font (`ascii.png`,
* `accented.png`, `nonlatin_european.png`), so it covers Latin, Latin Extended, Greek and Cyrillic
* exactly. Glyphs outside those sheets — CJK from the Unifont fallback, custom resource-pack glyphs
* — are measured as [DEFAULT_ADVANCE], so pass an override map for them via the `charWidths`
* parameter of the platform header components if they must be measured exactly.
*
* This is internal infrastructure — use the platform `TextAlignment` / header DSL instead.
*/
@InternalSurfApi
object DefaultFontWidths {

/** Advance of a glyph the vanilla font renders at its regular width (5px glyph + 1px spacing). */
const val DEFAULT_ADVANCE = 6

/** Extra advance the font renderer adds to every glyph of bold text. */
const val BOLD_EXTRA_ADVANCE = 1

/** Advance of a space, which the vanilla font takes from its space provider. */
const val SPACE_ADVANCE = 4

/**
* Every glyph of the vanilla font sheets whose advance is not [DEFAULT_ADVANCE], grouped by
* that advance. Generated by measuring the sheets, see [advances].
*/
private val ADVANCES_BY_WIDTH = mapOf(
2 to "!',.:;i|¡¦·ıǀǃʻˌ˙;·і׃ויןᛁᛌᛧ᛬ᴉị⁏ⁱ⏽⸵⹁יִ𐌹",
3 to "`l´ìíįĺļɩɭ̧ίιՙ՚՛՝լ׳᛫ᛵᶅᶖḷἱί‘’‚•′‵⁽⁾₍₎ꞁוֹ",
4 to "\"()*I[]t{}¨­¹ÌÍÎÏîïĪīĮİľŀţŧſƖƗƚƫƭǁǏǐțȴɨɪʇʈΙΪϊІЇїӀ՜՞׆זנװױײ჻ჼᚨᚭᚮᚲᚽᚿᛂᛆᛊᛍᛐᛓᛙᛚḮḯḹḻḽṫṭṯṱẗẛỈỉỊἰὶῑῒΐῙ‐‑‹›‼⁎⁑⁒⁺⁻⁼₁₊₋₌∥⋆⏴⏵⏸◘◦♀♩♭♮ײַוּ",
5 to "<>fkª°²³ºĨĩĬĭijķĸłťƙǩȊȋȶɟɬʞκгкѓќҕӄӷԼՒէւג״ჯᚠᚡᚦᚧᚩᚪᚫᚱᚳᚴᚵᚹᛈᛑᛒᛔᛕᛛᛩᛴᶂḟḬḭḱḳḵẜẝỽἶἷῐῖῗῘ“”„″‶⁊⁰⁴⁵⁶⁷⁸⁹ⁿ₀₂₃₄₅₆₇₈₉∁⌡▌●ꝛכּ",
7 to "@~«¶»ÐĐđħŁƀƄƅƇƈƉƑƓƝƬƳǤǥȽɃɇɈɉɊɋɌɍɖɗɠɲɳɻʄʆʐʛʠʮσϔЊЋДЦЪдщъыђњћѢѣѴѵѶѷҊҋҌҍҒҗҚҞҠҢңҤҥҨҩҲҳҵҶҷӅӆӉӊӍӎӹӺԆԇԎԏԤԥԦԧԨԩԬԭԮԯԳԴԹԺԽՂՄՌգդզժխղմնվքևֈႫႯჱᛃᛟᶁᶃᶆᶇᶍᶎᶏᶐᶑᶒᶓᶕᶗᶙỻỿἅἸἾἿῚΊ–‴‷⁋₣₫€₭₺₽₾⅟⇵√∝≈≡≢⏩⏪⏯▶▷◀◁⛄✔✘❌ⱥⴁⴂⴆⴎⴏⴝꚀꚁꚈꚉꚊꚋꚎꚒꚓꚗꜩꞕꞨꟆfl🔥🪓",
8 to "¤©®¼½¾ďĦʼnƁƊƠơƢƣƤƯưƴljȡȵɄɎɏɚɝɷʉʧʪʫʯΆΈΉΌΎΏωώЂЉЖФШЫЮюљҴҼҽҾҿӁӜӸԄԅտփႡႢႥႦႧႪႭႮႰႳႻჀჅლᛠᛢᛪᛳᵬᵭᵯᵰᵱᵲᵳᵴỚớỜờỞởỠỡỢợỨứỪừỬửỮữỰựỺἻἽὠὡὢὣὤὥὦὧὼώᾠᾡᾢᾣᾤᾥᾦᾧῲῳῴῶῷ…‰※⁂⁈⁉₦₩₪₱₳₴₷₻℗⅙⅚←→⇄∀∅∛∜∞≔⌀⌂⌘⌚⌛⌠⏭⏮⏳⏻⏼║╖╗╜╝╢╣░◎☂☃☄★☆☈☔☠☻♫♬⚀⚁⚂⚃⚄⚅⚐⚑⚓⚔⚗⛨✂✉✎❄❤⧈⭐⭘⯪⯫ⱢⱱꜨff�🔔🛡🧪🪣",
9 to "ƕǶʦʨЩҖҦҧԂԃԈԉԊԋԔԕԖԗԠԡԢԣᵺἈἉἎἏἘἙἨἩἮἯἺἼὈὉὙὟὨὩὮᾈᾉᾎᾏᾘᾙᾞᾟᾨᾩᾮᾺΆῈΈῊΉῪΎῬῸΌῺΏ—‱⁗™⅐⅑⅓⅔⅕⅖⅗⅛⅜⅝⅞↉↔⇏⇒⇔─┌└├┬┴┼═╒╓╔╘╙╚╞╟╠╤╥╦╧╨╩╪╫╬▀▄█▐▒▓☀☁☽⛈⛏ꚅꚖꜹꜻꜽffi🌊🌧🍖🎣🏹🔱🗡",
10 to "ÆæŒœDŽDždžLJLjNJNjnjǢǣDZDzdzǼǽȸȹɶʣʤʥʩϓӔӕԘԙԪԫᴂᴔᵫἊἋἌἍἚἛἜἝἪἫἬἭἹὊὋὌὍὛὝὪὫὬὭὯᾊᾋᾌᾍᾚᾛᾜᾝᾪᾫᾬᾭᾯ⁇₧₨₯№ℹ⅒⅘①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳ⒶⒷⒸⒹⒺⒻⒼⒽⒾⒿⓀⓁⓂⓃⓄⓅⓆⓇⓈⓉⓊⓋⓌⓍⓎⓏⓐⓑⓒⓓⓔⓕⓖⓗⓘⓙⓚⓛⓜⓝⓞⓟⓠⓡⓢⓣⓤⓥⓦⓧⓨⓩ⓪☐☑☒☜☞☮☯⚠ꚄꜲꜳꜴꜵꜶꜷꜸꜺꜼꝎꝏꭣfflstﬓﬔﬕﬖﬗ",
)

/**
* Per-code-point advance overrides for every glyph of the vanilla font sheets that is not
* [DEFAULT_ADVANCE] pixels wide.
*
* Measured off `ascii.png`, `accented.png` and `nonlatin_european.png` themselves, so these are
* the advances the client actually uses — Latin, Latin Extended, Greek and Cyrillic included.
*
* Suitable as the `charWidths` argument of [TextAlignmentMath.textWidth] when [DEFAULT_ADVANCE]
* is passed as its `charSize` and `0` as its `charSpacing`.
*/
val advances: Int2IntMap = Int2IntOpenHashMap(1100).apply {
// Measured off the vanilla font sheets (ascii.png, accented.png, nonlatin_european.png):
// advance = width of the drawn pixels plus the one pixel the renderer adds after a glyph.
// The space is the exception - it carries no pixels and comes from the space provider.
put(' '.code, SPACE_ADVANCE)

for ((advance, glyphs) in ADVANCES_BY_WIDTH) {
for (glyph in glyphs) put(glyph.code, advance)
}
Comment thread
TheBjoRedCraft marked this conversation as resolved.
}.freeze()

/**
* Returns the advance of [codePoint] in the vanilla font.
*
* [overrides] wins over [advances], which in turn wins over [DEFAULT_ADVANCE].
*
* @param codePoint the Unicode code point to measure
* @param bold whether the glyph is rendered bold
* @param overrides per-code-point advance overrides for glyphs this table does not cover
*/
fun charWidth(
codePoint: Int,
bold: Boolean = false,
overrides: Int2IntMap = Int2IntMaps.EMPTY_MAP,
): Int {
val advance = when {
overrides.containsKey(codePoint) -> overrides.get(codePoint)
advances.containsKey(codePoint) -> advances.get(codePoint)
else -> DEFAULT_ADVANCE
}

return advance + if (bold) BOLD_EXTRA_ADVANCE else 0
}

/**
* Returns the total number of pixels [text] advances the render cursor by when rendered in the
* vanilla font.
*
* Walks [text] per Unicode **code point**, so a surrogate pair counts as the single glyph it
* renders as. Returns `0` for an empty string.
*
* @param text the string to measure, exactly as it is rendered
* @param bold whether the text is rendered bold
* @param overrides per-code-point advance overrides for glyphs this table does not cover
*/
fun textWidth(
text: String,
bold: Boolean = false,
overrides: Int2IntMap = Int2IntMaps.EMPTY_MAP,
): Int {
var width = 0
var index = 0
while (index < text.length) {
val codePoint = text.codePointAt(index)
width += charWidth(codePoint, bold, overrides)
index += Character.charCount(codePoint)
}

return width
}

/**
* Returns the total number of pixels [component] advances the render cursor by when rendered in
* the vanilla font.
*
* Convenience wrapper around [layoutViewText] with the metrics of the vanilla font: no extra
* spacing and no casing transformation, so the component is measured exactly as it is.
*
* @param component the component to measure, exactly as it is rendered
* @param bold the [TextDecoration.BOLD] state inherited from the surrounding style
* @param overrides per-code-point advance overrides for glyphs this table does not cover
*/
fun componentWidth(
component: Component,
bold: Boolean = false,
overrides: Int2IntMap = Int2IntMaps.EMPTY_MAP,
): Int = layoutViewText(
component = component,
charSpacing = 0,
uppercase = false,
spaceShift = null,
bold = bold,
) { codePoint, glyphBold -> charWidth(codePoint, glyphBold, overrides) }.width
}
Original file line number Diff line number Diff line change
Expand Up @@ -92,15 +92,16 @@ object TextAlignmentMath {
val usableWidth = containerWidth - (padding * 2)
val freeSpace = usableWidth - textWidth

return leftShift + freeSpace + 1 + padding
return leftShift + freeSpace + padding
}

/**
* Pixel shift that centers a run of [textWidth] pixels horizontally within the container area.
*
* The free space is halved with [Math.floorDiv] so that a title wider than the container (which
* yields a negative free space) overflows evenly instead of being biased to one side by
* truncation towards zero.
* truncation towards zero. An odd free space leaves the extra pixel on the right, the way the
* client centres text itself.
*
* @param textWidth the rendered pixel width of the text, as returned by [textWidth]
* @param leftShift the base pixel offset of the container area
Expand All @@ -116,6 +117,6 @@ object TextAlignmentMath {
val usableWidth = containerWidth - (padding * 2)
val freeSpace = usableWidth - textWidth

return leftShift + Math.floorDiv(freeSpace, 2) + 1 + padding
return leftShift + Math.floorDiv(freeSpace, 2) + padding
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ package dev.slne.surf.api.core.inventory.framework.internal

import dev.slne.surf.api.core.messages.builder.SurfComponentBuilder
import dev.slne.surf.api.shared.api.util.InternalSurfApi
import it.unimi.dsi.fastutil.ints.Int2IntMap
import it.unimi.dsi.fastutil.ints.Int2IntMaps

/**
* Appends a single container component to this builder, surrounded by the shift glyphs that
Expand Down Expand Up @@ -42,63 +40,3 @@ fun SurfComponentBuilder.appendShiftedComponent(
append { text(ShiftGlyphs.renderShift(resetShift)) }
}
}

/**
* An inventory title that has been laid out for rendering.
*
* @property text the glyph-interleaved string to emit
* @property width the exact number of pixels [text] advances the render cursor by
*/
@InternalSurfApi
data class RenderedViewTitle(val text: String, val width: Int)

/**
* Lays out the rendered inventory title: every character is uppercased and inter-character
* spacing glyphs are inserted between them. Unless the title is right-aligned, a leading spacing
* glyph is prepended as well.
*
* The returned [RenderedViewTitle.width] is measured on the string that is actually emitted, so it
* accounts for the leading spacing glyph and for uppercasing that changes the glyph count (`ß`
* uppercases to `SS`). Text is walked per Unicode **code point**, so surrogate pairs are neither
* split apart nor counted twice.
*
* Shared by the per-platform `ViewContainerTitleComponent` implementations. This is internal
* infrastructure — use the platform DSL instead.
*
* @param title the plain-text inventory title
* @param charSize the pixel width of a single character in the title font
* @param charSpacing the pixel spacing to insert between each character
* @param alignRight `true` when the title is right-aligned
* @param charWidths per-code-point width overrides for glyphs that are not [charSize] wide
* @return the string to render together with its exact pixel width
*/
@InternalSurfApi
fun renderViewTitle(
title: String,
charSize: Int,
charSpacing: Int,
alignRight: Boolean,
charWidths: Int2IntMap = Int2IntMaps.EMPTY_MAP,
): RenderedViewTitle {
val uppercased = title.uppercase()
if (uppercased.isEmpty()) return RenderedViewTitle("", 0)

val spacing = ShiftGlyphs.renderShift(charSpacing)
val text = buildString {
if (!alignRight) append(spacing)

var index = 0
while (index < uppercased.length) {
if (index > 0) append(spacing)

val codePoint = uppercased.codePointAt(index)
appendCodePoint(codePoint)
index += Character.charCount(codePoint)
}
}

val width = TextAlignmentMath.textWidth(uppercased, charSize, charSpacing, charWidths) +
if (alignRight) 0 else charSpacing

return RenderedViewTitle(text, width)
}
Loading