diff --git a/BUILDING.md b/BUILDING.md new file mode 100644 index 0000000..14dc273 --- /dev/null +++ b/BUILDING.md @@ -0,0 +1,230 @@ +# Building imgui-java WCHAR32 Fork + +This fork adds `IMGUI_USE_WCHAR32=1` support so that ImGui can handle Unicode codepoints above U+FFFF (emoji, CJK extensions, etc.). Published as `org.nnstd.imgui:imgui-java-*:1.90.0-wchar32+mrlokopN`. + +## Repository + +- **Maven**: `https://mvn.mrlokop.dev/releases` +- **Git**: `https://git.mrlokop.dev/shlepa/imgui-java` + +## Coordinates + +```xml + + mrlokop + https://mvn.mrlokop.dev/releases + + + + org.nnstd.imgui + imgui-java-binding + 1.90.0-wchar32+mrlokop2 + + + org.nnstd.imgui + imgui-java-lwjgl3 + 1.90.0-wchar32+mrlokop2 + + + + org.nnstd.imgui + imgui-java-natives-macos + 1.90.0-wchar32+mrlokop2 + + + org.nnstd.imgui + imgui-java-natives-linux + 1.90.0-wchar32+mrlokop2 + + + org.nnstd.imgui + imgui-java-natives-windows + 1.90.0-wchar32+mrlokop2 + + + + org.nnstd.imgui + imgui-java-app + 1.90.0-wchar32+mrlokop2 + +``` + +For Gradle (Kotlin DSL): + +```kotlin +repositories { + maven("https://mvn.mrlokop.dev/releases") +} + +dependencies { + implementation("org.nnstd.imgui:imgui-java-binding:1.90.0-wchar32+mrlokop2") + implementation("org.nnstd.imgui:imgui-java-lwjgl3:1.90.0-wchar32+mrlokop2") + runtimeOnly("org.nnstd.imgui:imgui-java-natives-linux:1.90.0-wchar32+mrlokop2") +} +``` + +## Version History + +| Version | Linux toolchain | Notes | +|---------|-----------------|-------| +| `+mrlokop1` | musl (`x86_64-unknown-linux-musl`) | No glibc dependency; static stdlib | +| `+mrlokop2` | glibc (`x86_64-unknown-linux-gnu`) | Standard Linux distros; links libc.so.6 | + +## Prerequisites (macOS build host) + +All cross-compilation is done from a macOS machine. + +```bash +# Java 17 (build toolchain) +sdk install java 17.0.18.1-sem # via SDKMAN + +# Native build tools +brew install ant +brew install mingw-w64 # Windows cross-compiler + +# Linux glibc cross-compiler (for +mrlokop2 / recommended) +brew tap messense/macos-cross-toolchains +brew install x86_64-unknown-linux-gnu + +# Linux musl cross-compiler (for +mrlokop1 / no-glibc builds) +brew install x86_64-unknown-linux-musl +``` + +## Build Steps + +### 1. Clone and initialise submodules + +```bash +git clone https://git.mrlokop.dev/shlepa/imgui-java +cd imgui-java +git submodule update --init --recursive +``` + +### 2. Build vendored FreeType for each platform + +FreeType must be cross-compiled before the JNI natives. + +```bash +buildSrc/scripts/vendor_freetype.sh macos +buildSrc/scripts/vendor_freetype.sh linux +buildSrc/scripts/vendor_freetype.sh windows +``` + +Each invocation drops a static `libfreetype.a` into `build/vendor/freetype/lib/`. The macOS build uses `lipo` to create a universal (arm64 + x86_64) library automatically. + +### 3. Build JNI natives + +```bash +export JAVA_HOME=/path/to/java17 + +# macOS universal (arm64 + x86_64) +JAVA_HOME=$JAVA_HOME buildSrc/scripts/build.sh macos + +# Linux x86_64 (glibc) +JAVA_HOME=$JAVA_HOME buildSrc/scripts/build.sh linux + +# Windows x86_64 (mingw) +JAVA_HOME=$JAVA_HOME buildSrc/scripts/build.sh windows +``` + +Compiled natives land in: +- `bin/macos64/libimgui-java64.dylib` +- `bin/macosarm64/libimgui-java64.dylib` +- `bin/linux64/libimgui-java64.so` +- `bin/windows64/imgui-java64.dll` + +### 4. Bump the version (when releasing a new patch) + +Edit `build.gradle` (root), increment the `+mrlokopN` suffix: + +```groovy +version = '1.90.0-wchar32+mrlokop3' // was +mrlokop2 +``` + +### 5. Publish to Maven + +Publish each platform's natives separately because the artifact classifier differs. Run all four publish commands: + +```bash +JH=/path/to/java17 +GW="JAVA_HOME=$JH ./gradlew" + +# Binding + LWJGL3 (platform-independent JARs) +$GW imgui-binding:publishImguiPublicationToMrLokopRepository \ + imgui-lwjgl3:publishImguiPublicationToMrLokopRepository + +# Native artifacts (one per platform) +$GW imgui-binding-natives:publishImguiPublicationToMrLokopRepository -PdeployType=macos +$GW imgui-binding-natives:publishImguiPublicationToMrLokopRepository -PdeployType=linux +$GW imgui-binding-natives:publishImguiPublicationToMrLokopRepository -PdeployType=windows + +# Fat app JAR (all natives bundled) +$GW imgui-app:publishImguiPublicationToMrLokopRepository +``` + +## Switching Linux Toolchain (musl vs glibc) + +**glibc** (default, `+mrlokop2`) — standard Linux distributions: + +In `buildSrc/src/main/groovy/tool/generator/GenerateLibs.groovy`: +```groovy +linux64.compilerPrefix = 'x86_64-unknown-linux-gnu-' +``` + +In `buildSrc/scripts/vendor_freetype.sh`: +```bash +linux) build_freetype "-fPIC" "--host=x86_64-unknown-linux-gnu" "lib/libfreetype.a" "x86_64-unknown-linux-gnu-gcc" ;; +``` + +**musl** (`+mrlokop1`) — no glibc dependency, portable: + +In `GenerateLibs.groovy`: +```groovy +linux64.compilerPrefix = 'x86_64-unknown-linux-musl-' +linux64.linkerFlags = linux64.linkerFlags.replace('-Wl,-wrap,memcpy', '') +// After AntScriptGenerator.generate(), neutralise memcpy_wrap.c: +def memcpyWrap = new File("$jniDir/memcpy_wrap.c") +if (memcpyWrap.exists()) { + memcpyWrap.text = '/* disabled for musl */' +} +``` + +In `vendor_freetype.sh`: +```bash +linux) build_freetype "-fPIC" "--host=x86_64-unknown-linux-musl" "lib/libfreetype.a" "x86_64-unknown-linux-musl-gcc" ;; +``` + +## Key Changes vs Upstream + +| File | Change | +|------|--------| +| `imgui-binding/src/main/native/imconfig.h` | Uncommented `#define IMGUI_USE_WCHAR32` | +| `ImFontGlyphRangesBuilder.java` | `UNICODE_CODEPOINT_MAX=0x10FFFF`; `addChar(int)`; 32-bit LE encoding in `addRanges`/`buildRanges` | +| `ImFontConfig.java` | Added `setGlyphRanges(int[])` overload | +| `ImFontAtlas.java` | `RETURN_GLYPH_2_SHORT` macro uses `sizeof(ImWchar)/sizeof(jshort)` multiplier | +| `build.gradle` | Version hardcoded to `1.90.0-wchar32+mrlokopN` | +| `publish.gradle` | `groupId=org.nnstd.imgui`, repo=`https://mvn.mrlokop.dev/releases` | +| `GenerateLibs.groovy` | Linux uses `x86_64-unknown-linux-gnu-` prefix | +| `vendor_freetype.sh` | `--host=` flags for all platforms; macOS universal via `lipo` | + +## 32-bit Glyph Range Encoding + +With `IMGUI_USE_WCHAR32`, `ImWchar` is `uint32_t` (4 bytes). The Java binding stores glyph ranges as `short[]` where each 32-bit codepoint is packed as two little-endian `jshort`s: + +``` +ranges = [lo_lo, lo_hi, hi_lo, hi_hi, ..., 0, 0] + ^--- pair 1 ---^ ^--- pair 2+ ---^ terminator +``` + +Use `ImFontGlyphRangesBuilder.buildRanges()` or `ImFontConfig.setGlyphRanges(int[])` to handle this automatically: + +```java +// int[] variant (simplest) +fontConfig.setGlyphRanges(new int[]{ 0x1F000, 0x1F9FF, 0 }); + +// Builder variant +ImFontGlyphRangesBuilder builder = new ImFontGlyphRangesBuilder(); +builder.addText("Hello 🌍"); +short[] ranges = builder.buildRanges(); +fontConfig.setGlyphRanges(ranges); +``` diff --git a/bin/imgui-java64.dll b/bin/imgui-java64.dll index a2d8549..9118d81 100644 Binary files a/bin/imgui-java64.dll and b/bin/imgui-java64.dll differ diff --git a/bin/libimgui-java64.dylib b/bin/libimgui-java64.dylib index d9796b2..935b7fa 100644 Binary files a/bin/libimgui-java64.dylib and b/bin/libimgui-java64.dylib differ diff --git a/bin/libimgui-java64.so b/bin/libimgui-java64.so index 067c4e5..9793da9 100644 Binary files a/bin/libimgui-java64.so and b/bin/libimgui-java64.so differ diff --git a/build.gradle b/build.gradle index 9cdceb3..1f294d3 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ ext { allprojects { group = 'imgui-java' - version = 'git describe --tags --always'.execute().text.trim().substring(1) + version = '1.90.0-wchar32+mrlokop2' repositories { mavenCentral() diff --git a/buildSrc/scripts/vendor_freetype.sh b/buildSrc/scripts/vendor_freetype.sh index 5d973c7..f1c47b6 100755 --- a/buildSrc/scripts/vendor_freetype.sh +++ b/buildSrc/scripts/vendor_freetype.sh @@ -35,16 +35,23 @@ echo "FreeType unzipped to $LIBDIR" COMMON_FLAGS="--enable-static --disable-shared --without-zlib --without-bzip2 --without-png --without-harfbuzz --without-brotli" # Function to configure and build FreeType +# Args: cflags prefix output_dir [cc] build_freetype() { cflags=$1 prefix=$2 output_dir=$3 + cc=${4:-} echo "Cleaning previous builds..." make clean - echo "Configuring FreeType with CFLAGS='$cflags' and PREFIX='$prefix'..." - ./configure CFLAGS="$cflags" $COMMON_FLAGS $prefix + if [ -n "$cc" ]; then + echo "Configuring FreeType with CC='$cc' CFLAGS='$cflags' and PREFIX='$prefix'..." + ./configure CC="$cc" CFLAGS="$cflags" $COMMON_FLAGS $prefix + else + echo "Configuring FreeType with CFLAGS='$cflags' and PREFIX='$prefix'..." + ./configure CFLAGS="$cflags" $COMMON_FLAGS $prefix + fi if [ $? -ne 0 ]; then echo "Failed to configure FreeType" exit 1 @@ -82,13 +89,13 @@ case "$VTYPE" in build_freetype "" "--host=x86_64-w64-mingw32 --prefix=/usr/x86_64-w64-mingw32" "lib/libfreetype.a" ;; linux) - build_freetype "-fPIC" "" "lib/libfreetype.a" + build_freetype "-fPIC" "--host=x86_64-unknown-linux-gnu" "lib/libfreetype.a" "x86_64-unknown-linux-gnu-gcc" ;; macos) MACOS_VERSION=10.15 - build_freetype "-arch x86_64 -mmacosx-version-min=$MACOS_VERSION" "" "tmp/libfreetype-x86_64.a" - build_freetype "-arch arm64 -mmacosx-version-min=$MACOS_VERSION" "" "tmp/libfreetype-arm64.a" + build_freetype "-arch x86_64 -mmacosx-version-min=$MACOS_VERSION" "--host=x86_64-apple-darwin" "tmp/libfreetype-x86_64.a" + build_freetype "-arch arm64 -mmacosx-version-min=$MACOS_VERSION" "--host=aarch64-apple-darwin" "tmp/libfreetype-arm64.a" echo "Creating universal library using lipo..." lipo -create -output lib/libfreetype.a tmp/libfreetype-x86_64.a tmp/libfreetype-arm64.a diff --git a/buildSrc/src/main/groovy/tool/generator/GenerateLibs.groovy b/buildSrc/src/main/groovy/tool/generator/GenerateLibs.groovy index 3a0c000..5410c27 100644 --- a/buildSrc/src/main/groovy/tool/generator/GenerateLibs.groovy +++ b/buildSrc/src/main/groovy/tool/generator/GenerateLibs.groovy @@ -108,12 +108,14 @@ class GenerateLibs extends DefaultTask { if (forWindows) { def win64 = BuildTarget.newDefaultTarget(Os.Windows, Architecture.Bitness._64) + win64.compilerPrefix = 'x86_64-w64-mingw32-' addFreeTypeIfEnabled(win64) buildTargets += win64 } if (forLinux) { def linux64 = BuildTarget.newDefaultTarget(Os.Linux, Architecture.Bitness._64) + linux64.compilerPrefix = 'x86_64-unknown-linux-gnu-' addFreeTypeIfEnabled(linux64) buildTargets += linux64 } diff --git a/gradle.properties b/gradle.properties index ee149e8..16c38a8 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,4 @@ org.gradle.parallel=true org.gradle.caching=true org.gradle.jvmargs=-Xms2G -Xmx4G +org.gradle.java.installations.paths=/Users/user/.sdkman/candidates/java/8.0.472-amzn,/Users/user/.sdkman/candidates/java/17.0.18.1-sem,/Users/user/.sdkman/candidates/java/25-amzn diff --git a/imgui-binding/src/generated/java/imgui/ImFontAtlas.java b/imgui-binding/src/generated/java/imgui/ImFontAtlas.java index fed5a9f..c242d38 100644 --- a/imgui-binding/src/generated/java/imgui/ImFontAtlas.java +++ b/imgui-binding/src/generated/java/imgui/ImFontAtlas.java @@ -594,9 +594,11 @@ public final class ImFontAtlas extends ImGuiStructDestroyable { /*JNI #define RETURN_GLYPH_2_SHORT(glyphs) \ const ImWchar* ranges = glyphs; \ - int size = 0; \ + int pairCount = 0; \ for (; ranges[0]; ranges += 2) \ - size += 2; \ + pairCount++; \ + int shortsPerImWchar = (int)(sizeof(ImWchar) / sizeof(jshort)); \ + int size = pairCount * 2 * shortsPerImWchar + shortsPerImWchar; \ jshortArray jShorts = env->NewShortArray(size); \ env->SetShortArrayRegion(jShorts, 0, size, (jshort*)glyphs); \ return jShorts; diff --git a/imgui-binding/src/generated/java/imgui/ImFontConfig.java b/imgui-binding/src/generated/java/imgui/ImFontConfig.java index 4ee7811..138e0ea 100644 --- a/imgui-binding/src/generated/java/imgui/ImFontConfig.java +++ b/imgui-binding/src/generated/java/imgui/ImFontConfig.java @@ -347,6 +347,33 @@ public final class ImFontConfig extends ImGuiStructDestroyable { nSetGlyphRanges(glyphRanges); } + /** + * Convenience overload: encodes int[] codepoint pairs into WCHAR32 short[] layout and calls setGlyphRanges. + * Expects int[] pairs [ lo, hi, lo, hi, ..., 0 ] (zero-terminated). + * THE ARRAY DATA NEEDS TO PERSIST AS LONG AS THE FONT IS ALIVE. + */ + public void setGlyphRanges(final int[] codepoints) { + // count pairs + int pairCount = 0; + for (int i = 0; i + 1 < codepoints.length; i += 2) { + if (codepoints[i] == 0) break; + pairCount++; + } + // each pair = 4 shorts (2 per endpoint), plus 2 terminator shorts + final short[] encoded = new short[pairCount * 4 + 2]; + int out = 0; + for (int i = 0; i < pairCount; i++) { + final int lo = codepoints[i * 2]; + final int hi = codepoints[i * 2 + 1]; + encoded[out++] = (short) (lo & 0xFFFF); + encoded[out++] = (short) ((lo >> 16) & 0xFFFF); + encoded[out++] = (short) (hi & 0xFFFF); + encoded[out++] = (short) ((hi >> 16) & 0xFFFF); + } + // terminator already zero from array initialisation + setGlyphRanges(encoded); + } + private native void nSetGlyphRanges(short[] glyphRanges); /* THIS->GlyphRanges = glyphRanges != NULL ? (ImWchar*)&glyphRanges[0] : NULL; */ diff --git a/imgui-binding/src/generated/java/imgui/ImFontGlyphRangesBuilder.java b/imgui-binding/src/generated/java/imgui/ImFontGlyphRangesBuilder.java index b9f85ba..3384830 100644 --- a/imgui-binding/src/generated/java/imgui/ImFontGlyphRangesBuilder.java +++ b/imgui-binding/src/generated/java/imgui/ImFontGlyphRangesBuilder.java @@ -10,12 +10,14 @@ import java.util.List; * You can use the ImFontGlyphRangesBuilder helper to create glyph ranges based on text input. For example: for a game where your script is known, * if you can feed your entire script to it and only build the characters the game needs. *

- * Direct reimplementation of native ImFontGlyphRangesBuilder in java + * Direct reimplementation of native ImFontGlyphRangesBuilder in java. + * Built with IMGUI_USE_WCHAR32=1: supports full Unicode range U+0000..U+10FFFF. + * buildRanges() emits each range endpoint as two little-endian shorts (32-bit ImWchar layout). */ public final class ImFontGlyphRangesBuilder { - private static final int UNICODE_CODEPOINT_MAX = 0xFFFF; + private static final int UNICODE_CODEPOINT_MAX = 0x10FFFF; - // have to use type long because values are unsigned integers + // have to use type long because values are unsigned integers; sized the same way as native (divide by 8) private final long[] usedChars = new long[(UNICODE_CODEPOINT_MAX + 1) / 8]; public ImFontGlyphRangesBuilder() { @@ -23,56 +25,98 @@ public final class ImFontGlyphRangesBuilder { } /** - * Adds single character to resulting ranges + * Adds single character to resulting ranges (char overload for source compatibility) */ public void addChar(final char c) { setBit(c); } /** - * Adds all characters from the given string to resulting ranges + * Adds a Unicode codepoint (including SMP, U+10000..U+10FFFF) to resulting ranges */ - public void addText(final String text) { - for (int i = 0; i < text.length(); i++) { - addChar(text.charAt(i)); + public void addChar(final int codepoint) { + if (codepoint >= 0 && codepoint <= UNICODE_CODEPOINT_MAX) { + setBit(codepoint); } } /** - * Copies all given ranges to resulting ranges + * Adds all codepoints from the given string to resulting ranges. + * Handles supplementary characters (emoji, etc.) via codePointAt iteration. + */ + public void addText(final String text) { + int i = 0; + while (i < text.length()) { + final int cp = text.codePointAt(i); + addChar(cp); + i += Character.charCount(cp); + } + } + + /** + * Copies all given ranges to resulting ranges. + * Expects the WCHAR32 short[] layout: each 32-bit codepoint encoded as two little-endian shorts, + * pairs of codepoints form a [lo, hi] range, terminated by two zero shorts. + * Format: [ lo_lo16, lo_hi16, hi_lo16, hi_hi16, ..., 0, 0 ] */ public void addRanges(final short[] ranges) { - for (int i = 0; i < ranges.length; i += 2) { + for (int i = 0; i + 3 < ranges.length; i += 4) { + final int lo = (ranges[i] & 0xFFFF) | ((ranges[i + 1] & 0xFFFF) << 16); + final int hi = (ranges[i + 2] & 0xFFFF) | ((ranges[i + 3] & 0xFFFF) << 16); + if (lo == 0) { + break; + } + for (int k = lo; k <= hi && k <= UNICODE_CODEPOINT_MAX; k++) { + setBit(k); + } + } + } + + /** + * Copies all given ranges to resulting ranges. + * Expects int[] pairs: [ lo, hi, lo, hi, ..., 0 ] (zero-terminated). + */ + public void addRanges(final int[] ranges) { + for (int i = 0; i + 1 < ranges.length; i += 2) { if (ranges[i] == 0) { break; } - - for (int k = ranges[i]; k <= ranges[i + 1]; k++) { - addChar((char) k); + for (int k = ranges[i]; k <= ranges[i + 1] && k <= UNICODE_CODEPOINT_MAX; k++) { + setBit(k); } } } /** - * Builds the final result (ordered ranges with all the unique characters submitted) - * Result of this function can be directly passed to ImFontAtlas + * Builds the final result (ordered ranges with all the unique characters submitted). + * Result is in WCHAR32 short[] layout: each range endpoint is two little-endian shorts, + * terminated by two zero shorts. + * Format: [ lo_lo16, lo_hi16, hi_lo16, hi_hi16, ..., 0, 0 ] */ public short[] buildRanges() { final List out = new ArrayList<>(); for (int n = 0; n <= UNICODE_CODEPOINT_MAX; n++) { if (getBit(n)) { - out.add((short) n); + final int lo = n; while (n < UNICODE_CODEPOINT_MAX && getBit(n + 1)) { n++; } - out.add((short) n); + final int hi = n; + // emit lo as two LE shorts + out.add((short) (lo & 0xFFFF)); + out.add((short) ((lo >> 16) & 0xFFFF)); + // emit hi as two LE shorts + out.add((short) (hi & 0xFFFF)); + out.add((short) ((hi >> 16) & 0xFFFF)); } } - final short[] result = new short[out.size() + 1]; + // terminator: two zero shorts (= one 32-bit zero ImWchar) + final short[] result = new short[out.size() + 2]; for (int i = 0; i < out.size(); i++) { result[i] = out.get(i); } + result[result.length - 2] = 0; result[result.length - 1] = 0; return result; } diff --git a/imgui-binding/src/main/java/imgui/ImFontAtlas.java b/imgui-binding/src/main/java/imgui/ImFontAtlas.java index bab43a6..8e0f2be 100644 --- a/imgui-binding/src/main/java/imgui/ImFontAtlas.java +++ b/imgui-binding/src/main/java/imgui/ImFontAtlas.java @@ -256,9 +256,11 @@ public final class ImFontAtlas extends ImGuiStructDestroyable { /*JNI #define RETURN_GLYPH_2_SHORT(glyphs) \ const ImWchar* ranges = glyphs; \ - int size = 0; \ + int pairCount = 0; \ for (; ranges[0]; ranges += 2) \ - size += 2; \ + pairCount++; \ + int shortsPerImWchar = (int)(sizeof(ImWchar) / sizeof(jshort)); \ + int size = pairCount * 2 * shortsPerImWchar + shortsPerImWchar; \ jshortArray jShorts = env->NewShortArray(size); \ env->SetShortArrayRegion(jShorts, 0, size, (jshort*)glyphs); \ return jShorts; diff --git a/imgui-binding/src/main/java/imgui/ImFontConfig.java b/imgui-binding/src/main/java/imgui/ImFontConfig.java index c366149..107bcf9 100644 --- a/imgui-binding/src/main/java/imgui/ImFontConfig.java +++ b/imgui-binding/src/main/java/imgui/ImFontConfig.java @@ -124,6 +124,33 @@ public final class ImFontConfig extends ImGuiStructDestroyable { nSetGlyphRanges(glyphRanges); } + /** + * Convenience overload: encodes int[] codepoint pairs into WCHAR32 short[] layout and calls setGlyphRanges. + * Expects int[] pairs [ lo, hi, lo, hi, ..., 0 ] (zero-terminated). + * THE ARRAY DATA NEEDS TO PERSIST AS LONG AS THE FONT IS ALIVE. + */ + public void setGlyphRanges(final int[] codepoints) { + // count pairs + int pairCount = 0; + for (int i = 0; i + 1 < codepoints.length; i += 2) { + if (codepoints[i] == 0) break; + pairCount++; + } + // each pair = 4 shorts (2 per endpoint), plus 2 terminator shorts + final short[] encoded = new short[pairCount * 4 + 2]; + int out = 0; + for (int i = 0; i < pairCount; i++) { + final int lo = codepoints[i * 2]; + final int hi = codepoints[i * 2 + 1]; + encoded[out++] = (short) (lo & 0xFFFF); + encoded[out++] = (short) ((lo >> 16) & 0xFFFF); + encoded[out++] = (short) (hi & 0xFFFF); + encoded[out++] = (short) ((hi >> 16) & 0xFFFF); + } + // terminator already zero from array initialisation + setGlyphRanges(encoded); + } + private native void nSetGlyphRanges(short[] glyphRanges); /* THIS->GlyphRanges = glyphRanges != NULL ? (ImWchar*)&glyphRanges[0] : NULL; */ diff --git a/imgui-binding/src/main/java/imgui/ImFontGlyphRangesBuilder.java b/imgui-binding/src/main/java/imgui/ImFontGlyphRangesBuilder.java index b9f85ba..3384830 100644 --- a/imgui-binding/src/main/java/imgui/ImFontGlyphRangesBuilder.java +++ b/imgui-binding/src/main/java/imgui/ImFontGlyphRangesBuilder.java @@ -10,12 +10,14 @@ import java.util.List; * You can use the ImFontGlyphRangesBuilder helper to create glyph ranges based on text input. For example: for a game where your script is known, * if you can feed your entire script to it and only build the characters the game needs. *

- * Direct reimplementation of native ImFontGlyphRangesBuilder in java + * Direct reimplementation of native ImFontGlyphRangesBuilder in java. + * Built with IMGUI_USE_WCHAR32=1: supports full Unicode range U+0000..U+10FFFF. + * buildRanges() emits each range endpoint as two little-endian shorts (32-bit ImWchar layout). */ public final class ImFontGlyphRangesBuilder { - private static final int UNICODE_CODEPOINT_MAX = 0xFFFF; + private static final int UNICODE_CODEPOINT_MAX = 0x10FFFF; - // have to use type long because values are unsigned integers + // have to use type long because values are unsigned integers; sized the same way as native (divide by 8) private final long[] usedChars = new long[(UNICODE_CODEPOINT_MAX + 1) / 8]; public ImFontGlyphRangesBuilder() { @@ -23,56 +25,98 @@ public final class ImFontGlyphRangesBuilder { } /** - * Adds single character to resulting ranges + * Adds single character to resulting ranges (char overload for source compatibility) */ public void addChar(final char c) { setBit(c); } /** - * Adds all characters from the given string to resulting ranges + * Adds a Unicode codepoint (including SMP, U+10000..U+10FFFF) to resulting ranges */ - public void addText(final String text) { - for (int i = 0; i < text.length(); i++) { - addChar(text.charAt(i)); + public void addChar(final int codepoint) { + if (codepoint >= 0 && codepoint <= UNICODE_CODEPOINT_MAX) { + setBit(codepoint); } } /** - * Copies all given ranges to resulting ranges + * Adds all codepoints from the given string to resulting ranges. + * Handles supplementary characters (emoji, etc.) via codePointAt iteration. + */ + public void addText(final String text) { + int i = 0; + while (i < text.length()) { + final int cp = text.codePointAt(i); + addChar(cp); + i += Character.charCount(cp); + } + } + + /** + * Copies all given ranges to resulting ranges. + * Expects the WCHAR32 short[] layout: each 32-bit codepoint encoded as two little-endian shorts, + * pairs of codepoints form a [lo, hi] range, terminated by two zero shorts. + * Format: [ lo_lo16, lo_hi16, hi_lo16, hi_hi16, ..., 0, 0 ] */ public void addRanges(final short[] ranges) { - for (int i = 0; i < ranges.length; i += 2) { + for (int i = 0; i + 3 < ranges.length; i += 4) { + final int lo = (ranges[i] & 0xFFFF) | ((ranges[i + 1] & 0xFFFF) << 16); + final int hi = (ranges[i + 2] & 0xFFFF) | ((ranges[i + 3] & 0xFFFF) << 16); + if (lo == 0) { + break; + } + for (int k = lo; k <= hi && k <= UNICODE_CODEPOINT_MAX; k++) { + setBit(k); + } + } + } + + /** + * Copies all given ranges to resulting ranges. + * Expects int[] pairs: [ lo, hi, lo, hi, ..., 0 ] (zero-terminated). + */ + public void addRanges(final int[] ranges) { + for (int i = 0; i + 1 < ranges.length; i += 2) { if (ranges[i] == 0) { break; } - - for (int k = ranges[i]; k <= ranges[i + 1]; k++) { - addChar((char) k); + for (int k = ranges[i]; k <= ranges[i + 1] && k <= UNICODE_CODEPOINT_MAX; k++) { + setBit(k); } } } /** - * Builds the final result (ordered ranges with all the unique characters submitted) - * Result of this function can be directly passed to ImFontAtlas + * Builds the final result (ordered ranges with all the unique characters submitted). + * Result is in WCHAR32 short[] layout: each range endpoint is two little-endian shorts, + * terminated by two zero shorts. + * Format: [ lo_lo16, lo_hi16, hi_lo16, hi_hi16, ..., 0, 0 ] */ public short[] buildRanges() { final List out = new ArrayList<>(); for (int n = 0; n <= UNICODE_CODEPOINT_MAX; n++) { if (getBit(n)) { - out.add((short) n); + final int lo = n; while (n < UNICODE_CODEPOINT_MAX && getBit(n + 1)) { n++; } - out.add((short) n); + final int hi = n; + // emit lo as two LE shorts + out.add((short) (lo & 0xFFFF)); + out.add((short) ((lo >> 16) & 0xFFFF)); + // emit hi as two LE shorts + out.add((short) (hi & 0xFFFF)); + out.add((short) ((hi >> 16) & 0xFFFF)); } } - final short[] result = new short[out.size() + 1]; + // terminator: two zero shorts (= one 32-bit zero ImWchar) + final short[] result = new short[out.size() + 2]; for (int i = 0; i < out.size(); i++) { result[i] = out.get(i); } + result[result.length - 2] = 0; result[result.length - 1] = 0; return result; } diff --git a/imgui-binding/src/main/native/imconfig.h b/imgui-binding/src/main/native/imconfig.h index 917f031..52d9912 100644 --- a/imgui-binding/src/main/native/imconfig.h +++ b/imgui-binding/src/main/native/imconfig.h @@ -59,7 +59,7 @@ //#define IMGUI_USE_BGRA_PACKED_COLOR //---- Use 32-bit for ImWchar (default is 16-bit) to support unicode planes 1-16. (e.g. point beyond 0xFFFF like emoticons, dingbats, symbols, shapes, ancient languages, etc...) -//#define IMGUI_USE_WCHAR32 +#define IMGUI_USE_WCHAR32 //---- Avoid multiple STB libraries implementations, or redefine path/filenames to prioritize another version // By default the embedded implementations are declared static and not available outside of Dear ImGui sources files. diff --git a/publish.gradle b/publish.gradle index 270918c..996489e 100644 --- a/publish.gradle +++ b/publish.gradle @@ -12,19 +12,17 @@ ext.configurePublishing = { packageName, packageDesc, packageVersion -> publishing { repositories { maven { - name = 'MavenCentral' - def releasesRepoUrl = 'https://ossrh-staging-api.central.sonatype.com/service/local/staging/deploy/maven2' - def snapshotsRepoUrl = 'https://central.sonatype.com/repository/maven-snapshots' - url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl + name = 'MrLokop' + url = 'https://mvn.mrlokop.dev/releases' credentials { - username = System.getenv('NEXUS_UPD_ID')?.trim() ?: '' - password = System.getenv('NEXUS_UPD_PASS')?.trim() ?: '' + username = 'AutoRobot' + password = '8Mr3OAv4WcRNHZS3lAVDx32sWt71YS7GsBvN7TZhCGMmDeSz' } } } publications { imgui(MavenPublication) { - groupId = 'io.github.spair' + groupId = 'org.nnstd.imgui' artifactId = packageName version = packageVersion diff --git a/settings.gradle.kts b/settings.gradle.kts index 2029f83..96c2c94 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,5 +1,5 @@ plugins { - id("org.gradle.toolchains.foojay-resolver-convention") version("0.10.0") + id("org.gradle.toolchains.foojay-resolver-convention") version("0.9.0") } rootProject.name = "imgui-java"