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
2 changes: 1 addition & 1 deletion dotlottie/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ dotlottieRust {
}

group = "com.github.LottieFiles"
version = "0.14.3"
version = "0.15.0"

android {
namespace = "com.lottiefiles.dotlottie.core"
Expand Down
24 changes: 24 additions & 0 deletions dotlottie/src/main/cpp/jni_bridge.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -853,6 +854,27 @@ jint nativeSetTextSlot(JNIEnv *env, jclass, jlong ptr, jstring slotId,
return static_cast<jint>(result);
}

jstring nativeGetSlotStr(JNIEnv *env, jclass, jlong ptr, jstring slotId) {
auto *player = reinterpret_cast<dotlottiePlayer *>(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;
}
Comment thread
afsalz marked this conversation as resolved.

jint nativeSetVectorSlot(JNIEnv *env, jclass, jlong ptr, jstring slotId,
jfloat x, jfloat y) {
auto *player = reinterpret_cast<dotlottiePlayer *>(ptr);
Expand Down Expand Up @@ -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},
Expand Down
2 changes: 1 addition & 1 deletion dotlottie/src/main/cpp/version.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
dlplayer-version=0.1.58-2ce5e48
dlplayer-version=0.1.58-1522301
api-type=c-api
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand Down
Binary file modified dotlottie/src/main/jniLibs/arm64-v8a/libdotlottie_player.so
Binary file not shown.
Binary file modified dotlottie/src/main/jniLibs/armeabi-v7a/libdotlottie_player.so
Binary file not shown.
Binary file modified dotlottie/src/main/jniLibs/x86/libdotlottie_player.so
Binary file not shown.
Binary file modified dotlottie/src/main/jniLibs/x86_64/libdotlottie_player.so
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Comment thread
afsalz marked this conversation as resolved.

private fun getTextDocument(slotId: String): TextDocument? {
val json = JNI.nativeGetSlotStr(nativePtr, slotId)
return TextDocument.fromSlotJson(json)
}

fun setVectorSlot(slotId: String, vector: PointF): Boolean {
Expand Down
128 changes: 128 additions & 0 deletions dotlottie/src/main/kotlin/com/dotlottie/dlplayer/TextDocument.kt
Original file line number Diff line number Diff line change
@@ -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.
Comment thread
afsalz marked this conversation as resolved.
*
* 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<Float>? = null,
val strokeColor: List<Float>? = 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<Float>? = null,
val wrapPosition: List<Float>? = 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":<frame>,"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<Float> = 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<Float> = 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<Float>.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<Float>? {
val arr = optJSONArray(key) ?: return null
return (0 until arr.length()).map { arr.getDouble(it).toFloat() }
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading