diff --git a/dotlottie/build.gradle.kts b/dotlottie/build.gradle.kts index f571e56..dd9a69f 100644 --- a/dotlottie/build.gradle.kts +++ b/dotlottie/build.gradle.kts @@ -37,7 +37,7 @@ dotlottieRust { } group = "com.github.LottieFiles" -version = "0.14.3" +version = "0.15.0" android { namespace = "com.lottiefiles.dotlottie.core" diff --git a/dotlottie/src/main/cpp/jni_bridge.cpp b/dotlottie/src/main/cpp/jni_bridge.cpp index 508c510..d027bd8 100644 --- a/dotlottie/src/main/cpp/jni_bridge.cpp +++ b/dotlottie/src/main/cpp/jni_bridge.cpp @@ -142,6 +142,7 @@ static jint nativeSetImageSlotPath(JNIEnv *env, jclass, jlong ptr, jstring slotId, jstring path); static jint nativeSetImageSlotDataUrl(JNIEnv *env, jclass, jlong ptr, jstring slotId, jstring dataUrl); +static jstring nativeGetSlotStr(JNIEnv *env, jclass, jlong ptr, jstring slotId); // Viewport static jint nativeSetViewport(JNIEnv *env, jclass, jlong ptr, jint x, jint y, @@ -853,6 +854,27 @@ jint nativeSetTextSlot(JNIEnv *env, jclass, jlong ptr, jstring slotId, return static_cast(result); } +jstring nativeGetSlotStr(JNIEnv *env, jclass, jlong ptr, jstring slotId) { + auto *player = reinterpret_cast(ptr); + const char *cSlotId = env->GetStringUTFChars(slotId, nullptr); + uintptr_t size = 0; + auto res = dotlottie_get_slot_str(player, cSlotId, nullptr, &size); + if (res != dotlottieDotLottieResult::Success || size == 0) { + env->ReleaseStringUTFChars(slotId, cSlotId); + return env->NewStringUTF(""); + } + char *buf = (char *)malloc(size); + if (!buf) { + env->ReleaseStringUTFChars(slotId, cSlotId); + return env->NewStringUTF(""); + } + dotlottie_get_slot_str(player, cSlotId, buf, nullptr); + jstring result = env->NewStringUTF(buf); + free(buf); + env->ReleaseStringUTFChars(slotId, cSlotId); + return result; +} + jint nativeSetVectorSlot(JNIEnv *env, jclass, jlong ptr, jstring slotId, jfloat x, jfloat y) { auto *player = reinterpret_cast(ptr); @@ -1631,6 +1653,8 @@ static JNINativeMethod playerMethods[] = { {"nativeSetImageSlotDataUrl", "(JLjava/lang/String;Ljava/lang/String;)I", (void *)nativeSetImageSlotDataUrl}, + {"nativeGetSlotStr", "(JLjava/lang/String;)Ljava/lang/String;", + (void *)nativeGetSlotStr}, // Viewport {"nativeSetViewport", "(JIIII)I", (void *)nativeSetViewport}, diff --git a/dotlottie/src/main/cpp/version.txt b/dotlottie/src/main/cpp/version.txt index 9725655..0421f3e 100644 --- a/dotlottie/src/main/cpp/version.txt +++ b/dotlottie/src/main/cpp/version.txt @@ -1,2 +1,2 @@ -dlplayer-version=0.1.58-2ce5e48 +dlplayer-version=0.1.58-1522301 api-type=c-api diff --git a/dotlottie/src/main/java/com/lottiefiles/dotlottie/core/compose/runtime/DotLottieController.kt b/dotlottie/src/main/java/com/lottiefiles/dotlottie/core/compose/runtime/DotLottieController.kt index ea4b741..9e57aa2 100644 --- a/dotlottie/src/main/java/com/lottiefiles/dotlottie/core/compose/runtime/DotLottieController.kt +++ b/dotlottie/src/main/java/com/lottiefiles/dotlottie/core/compose/runtime/DotLottieController.kt @@ -9,6 +9,7 @@ import com.dotlottie.dlplayer.Fit import com.dotlottie.dlplayer.Layout import com.dotlottie.dlplayer.Manifest import com.dotlottie.dlplayer.Marker +import com.dotlottie.dlplayer.TextDocument import com.dotlottie.dlplayer.Mode import com.dotlottie.dlplayer.OpenUrlPolicy import com.dotlottie.dlplayer.createDefaultOpenUrlPolicy @@ -414,6 +415,10 @@ class DotLottieController { return dlplayer?.setTextSlot(slotId, text) ?: false } + fun setTextSlot(slotId: String, document: TextDocument): Boolean { + return dlplayer?.setTextSlot(slotId, document) ?: false + } + fun setVectorSlot(slotId: String, vector: PointF): Boolean { return dlplayer?.setVectorSlot(slotId, vector) ?: false } diff --git a/dotlottie/src/main/java/com/lottiefiles/dotlottie/core/drawable/DotLottieDrawable.kt b/dotlottie/src/main/java/com/lottiefiles/dotlottie/core/drawable/DotLottieDrawable.kt index 63c6a5b..80ba7c2 100644 --- a/dotlottie/src/main/java/com/lottiefiles/dotlottie/core/drawable/DotLottieDrawable.kt +++ b/dotlottie/src/main/java/com/lottiefiles/dotlottie/core/drawable/DotLottieDrawable.kt @@ -18,6 +18,7 @@ import com.dotlottie.dlplayer.Config import com.dotlottie.dlplayer.Event import com.dotlottie.dlplayer.Layout import com.dotlottie.dlplayer.Manifest +import com.dotlottie.dlplayer.TextDocument import com.dotlottie.dlplayer.Marker import com.dotlottie.dlplayer.Mode import com.dotlottie.dlplayer.OpenUrlPolicy @@ -447,6 +448,12 @@ class DotLottieDrawable( return result } + fun setTextSlot(slotId: String, document: TextDocument): Boolean { + val result = dlPlayer?.setTextSlot(slotId, document) ?: false + if (result) scheduleFrame(forceUpdate = true) + return result + } + fun setVectorSlot(slotId: String, vector: PointF): Boolean { val result = dlPlayer?.setVectorSlot(slotId, vector) ?: false if (result) scheduleFrame(forceUpdate = true) diff --git a/dotlottie/src/main/java/com/lottiefiles/dotlottie/core/widget/DotLottieAnimation.kt b/dotlottie/src/main/java/com/lottiefiles/dotlottie/core/widget/DotLottieAnimation.kt index c5fa310..f6503b0 100644 --- a/dotlottie/src/main/java/com/lottiefiles/dotlottie/core/widget/DotLottieAnimation.kt +++ b/dotlottie/src/main/java/com/lottiefiles/dotlottie/core/widget/DotLottieAnimation.kt @@ -18,6 +18,7 @@ import com.dotlottie.dlplayer.Manifest import com.dotlottie.dlplayer.Marker import com.dotlottie.dlplayer.Mode import com.dotlottie.dlplayer.OpenUrlPolicy +import com.dotlottie.dlplayer.TextDocument import com.dotlottie.dlplayer.createDefaultLayout import com.dotlottie.dlplayer.createDefaultOpenUrlPolicy import com.lottiefiles.dotlottie.core.R @@ -237,6 +238,10 @@ class DotLottieAnimation @JvmOverloads constructor( return mLottieDrawable?.setTextSlot(slotId, text) ?: false } + fun setTextSlot(slotId: String, document: TextDocument): Boolean { + return mLottieDrawable?.setTextSlot(slotId, document) ?: false + } + fun setVectorSlot(slotId: String, vector: PointF): Boolean { return mLottieDrawable?.setVectorSlot(slotId, vector) ?: false } diff --git a/dotlottie/src/main/java/com/lottiefiles/dotlottie/core/widget/DotLottieGLAnimation.kt b/dotlottie/src/main/java/com/lottiefiles/dotlottie/core/widget/DotLottieGLAnimation.kt index 5241527..02ead59 100644 --- a/dotlottie/src/main/java/com/lottiefiles/dotlottie/core/widget/DotLottieGLAnimation.kt +++ b/dotlottie/src/main/java/com/lottiefiles/dotlottie/core/widget/DotLottieGLAnimation.kt @@ -24,6 +24,7 @@ import com.dotlottie.dlplayer.Marker import com.dotlottie.dlplayer.Mode import com.dotlottie.dlplayer.OpenUrlPolicy import com.dotlottie.dlplayer.StateMachinePlayerEvent +import com.dotlottie.dlplayer.TextDocument import com.dotlottie.dlplayer.createDefaultLayout import com.dotlottie.dlplayer.createDefaultOpenUrlPolicy import com.lottiefiles.dotlottie.core.ExperimentalDotLottieGLApi @@ -786,6 +787,10 @@ class DotLottieGLAnimation @JvmOverloads constructor( return dlPlayer?.setTextSlot(slotId, text) ?: false } + fun setTextSlot(slotId: String, document: TextDocument): Boolean { + return dlPlayer?.setTextSlot(slotId, document) ?: false + } + fun setVectorSlot(slotId: String, vector: PointF): Boolean { return dlPlayer?.setVectorSlot(slotId, vector) ?: false } diff --git a/dotlottie/src/main/jniLibs/arm64-v8a/libdotlottie_player.so b/dotlottie/src/main/jniLibs/arm64-v8a/libdotlottie_player.so index e2d1513..cd298fc 100755 Binary files a/dotlottie/src/main/jniLibs/arm64-v8a/libdotlottie_player.so and b/dotlottie/src/main/jniLibs/arm64-v8a/libdotlottie_player.so differ diff --git a/dotlottie/src/main/jniLibs/armeabi-v7a/libdotlottie_player.so b/dotlottie/src/main/jniLibs/armeabi-v7a/libdotlottie_player.so index 79192f4..f6a3928 100755 Binary files a/dotlottie/src/main/jniLibs/armeabi-v7a/libdotlottie_player.so and b/dotlottie/src/main/jniLibs/armeabi-v7a/libdotlottie_player.so differ diff --git a/dotlottie/src/main/jniLibs/x86/libdotlottie_player.so b/dotlottie/src/main/jniLibs/x86/libdotlottie_player.so index da2e563..c47cd93 100755 Binary files a/dotlottie/src/main/jniLibs/x86/libdotlottie_player.so and b/dotlottie/src/main/jniLibs/x86/libdotlottie_player.so differ diff --git a/dotlottie/src/main/jniLibs/x86_64/libdotlottie_player.so b/dotlottie/src/main/jniLibs/x86_64/libdotlottie_player.so index bbb4219..187655d 100755 Binary files a/dotlottie/src/main/jniLibs/x86_64/libdotlottie_player.so and b/dotlottie/src/main/jniLibs/x86_64/libdotlottie_player.so differ diff --git a/dotlottie/src/main/kotlin/com/dotlottie/dlplayer/DotLottiePlayer.kt b/dotlottie/src/main/kotlin/com/dotlottie/dlplayer/DotLottiePlayer.kt index ade3784..d72a291 100644 --- a/dotlottie/src/main/kotlin/com/dotlottie/dlplayer/DotLottiePlayer.kt +++ b/dotlottie/src/main/kotlin/com/dotlottie/dlplayer/DotLottiePlayer.kt @@ -2,6 +2,7 @@ package com.dotlottie.dlplayer import android.graphics.Color import android.graphics.PointF +import android.util.Log import androidx.annotation.ColorInt import com.lottiefiles.dotlottie.core.jni.DotLottiePlayer as JNI import org.json.JSONObject @@ -265,8 +266,28 @@ class DotLottiePlayer { } fun setTextSlot(slotId: String, text: String): Boolean { - val result = JNI.nativeSetTextSlot(nativePtr, slotId, text) - return result == 0 + val current = getTextDocument(slotId) + if (current == null) { + Log.w( + "DotLottie", + "setTextSlot: no text document found for slot \"$slotId\"; " + + "pass a TextDocument to set its styling explicitly.", + ) + return false + } + return setTextSlot(slotId, current.copy(text = text)) + } + + fun setTextSlot(slotId: String, document: TextDocument): Boolean { + val slotsJson = JSONObject().apply { + put(slotId, JSONObject().apply { put("p", JSONObject(document.toSlotJson())) }) + }.toString() + return JNI.nativeSetSlotsStr(nativePtr, slotsJson) == 0 + } + + private fun getTextDocument(slotId: String): TextDocument? { + val json = JNI.nativeGetSlotStr(nativePtr, slotId) + return TextDocument.fromSlotJson(json) } fun setVectorSlot(slotId: String, vector: PointF): Boolean { diff --git a/dotlottie/src/main/kotlin/com/dotlottie/dlplayer/TextDocument.kt b/dotlottie/src/main/kotlin/com/dotlottie/dlplayer/TextDocument.kt new file mode 100644 index 0000000..bf3ecba --- /dev/null +++ b/dotlottie/src/main/kotlin/com/dotlottie/dlplayer/TextDocument.kt @@ -0,0 +1,128 @@ +package com.dotlottie.dlplayer + +import android.graphics.Color +import androidx.annotation.ColorInt +import org.json.JSONArray +import org.json.JSONObject + +/** + * A Lottie text document used to drive a text slot. + * + * Field names are friendly Kotlin names; they are serialized to/from the short + * Lottie keys used by the native player (t, f, s, fc, sc, sw, of, lh, tr, j, ca, + * ls, sz, ps). Only [text] is required — leave the rest null to keep the value + * already present in the animation for that property. + * + * Colors are RGBA components in the 0..1 range, e.g. red = [1f, 0f, 0f, 1f]. + * Use [fillColorOf] / [strokeColorOf] to build them from an Android `@ColorInt`. + */ +data class TextDocument( + val text: String, + val fontName: String? = null, + val fontSize: Float? = null, + val fillColor: List? = null, + val strokeColor: List? = null, + val strokeWidth: Float? = null, + val strokeOverFill: Boolean? = null, + val lineHeight: Float? = null, + val tracking: Float? = null, + val justify: Int? = null, + val textCaps: Int? = null, + val baselineShift: Float? = null, + val wrapSize: List? = null, + val wrapPosition: List? = null, +) { + /** Serializes this document to the Lottie text-document object (the `s` payload). */ + internal fun toDocumentJson(): JSONObject = JSONObject().apply { + put("t", text) + fontName?.let { put("f", it) } + fontSize?.let { put("s", it.toDouble()) } + fillColor?.let { put("fc", it.toJsonArray()) } + strokeColor?.let { put("sc", it.toJsonArray()) } + strokeWidth?.let { put("sw", it.toDouble()) } + strokeOverFill?.let { put("of", it) } + lineHeight?.let { put("lh", it.toDouble()) } + tracking?.let { put("tr", it.toDouble()) } + justify?.let { put("j", it) } + textCaps?.let { put("ca", it) } + baselineShift?.let { put("ls", it.toDouble()) } + wrapSize?.let { put("sz", it.toJsonArray()) } + wrapPosition?.let { put("ps", it.toJsonArray()) } + } + + /** + * Serializes this document to a full text-slot JSON string + * (`{"k":[{"t":,"s":{...}}]}`), the format accepted by the native + * `set_slot_str` API. + */ + fun toSlotJson(frame: Int = 0): String { + val keyframe = JSONObject().apply { + put("t", frame) + put("s", toDocumentJson()) + } + return JSONObject().apply { + put("k", JSONArray().put(keyframe)) + }.toString() + } + + companion object { + /** Builds an RGBA 0..1 list from an Android `@ColorInt`. */ + fun fillColorOf(@ColorInt color: Int): List = listOf( + Color.red(color) / 255f, + Color.green(color) / 255f, + Color.blue(color) / 255f, + Color.alpha(color) / 255f, + ) + + /** Alias of [fillColorOf] for stroke colors. */ + fun strokeColorOf(@ColorInt color: Int): List = fillColorOf(color) + + /** + * Parses a text-slot JSON string (as returned by the native `get_slot_str`) + * into a [TextDocument], using the first keyframe's document. Returns null + * if the JSON does not contain a usable text document. + */ + fun fromSlotJson(json: String): TextDocument? { + if (json.isBlank()) return null + return runCatching { + val root = JSONObject(json) + val keyframes = root.optJSONArray("k") ?: return null + if (keyframes.length() == 0) return null + val doc = keyframes.getJSONObject(0).optJSONObject("s") ?: return null + TextDocument( + text = doc.optString("t", ""), + fontName = doc.optStringOrNull("f"), + fontSize = doc.optFloatOrNull("s"), + fillColor = doc.optFloatList("fc"), + strokeColor = doc.optFloatList("sc"), + strokeWidth = doc.optFloatOrNull("sw"), + strokeOverFill = if (doc.has("of")) doc.optBoolean("of") else null, + lineHeight = doc.optFloatOrNull("lh"), + tracking = doc.optFloatOrNull("tr"), + justify = doc.optIntOrNull("j"), + textCaps = doc.optIntOrNull("ca"), + baselineShift = doc.optFloatOrNull("ls"), + wrapSize = doc.optFloatList("sz"), + wrapPosition = doc.optFloatList("ps"), + ) + }.getOrNull() + } + } +} + +private fun List.toJsonArray(): JSONArray = + JSONArray().also { arr -> forEach { arr.put(it.toDouble()) } } + +private fun JSONObject.optStringOrNull(key: String): String? = + if (has(key) && !isNull(key)) getString(key) else null + +private fun JSONObject.optFloatOrNull(key: String): Float? = + if (has(key) && !isNull(key)) getDouble(key).toFloat() else null + +private fun JSONObject.optIntOrNull(key: String): Int? = + if (has(key) && !isNull(key)) getInt(key) else null + +private fun JSONObject.optFloatList(key: String): List? { + val arr = optJSONArray(key) ?: return null + return (0 until arr.length()).map { arr.getDouble(it).toFloat() } +} diff --git a/dotlottie/src/main/kotlin/com/lottiefiles/dotlottie/core/jni/DotLottiePlayer.kt b/dotlottie/src/main/kotlin/com/lottiefiles/dotlottie/core/jni/DotLottiePlayer.kt index c9ad8a3..4f6eaa8 100644 --- a/dotlottie/src/main/kotlin/com/lottiefiles/dotlottie/core/jni/DotLottiePlayer.kt +++ b/dotlottie/src/main/kotlin/com/lottiefiles/dotlottie/core/jni/DotLottiePlayer.kt @@ -294,6 +294,9 @@ object DotLottiePlayer { @JvmStatic external fun nativeSetImageSlotDataUrl(ptr: Long, slotId: String, dataUrl: String): Int + @JvmStatic + external fun nativeGetSlotStr(ptr: Long, slotId: String): String + // ==================== Viewport ==================== @JvmStatic