From 7d69352eda7b8504aa1cdf7a96b9cee9c9543467 Mon Sep 17 00:00:00 2001 From: Patrick Kabwe Date: Sun, 28 Sep 2025 23:59:58 +0200 Subject: [PATCH 01/12] feat(android): add NitroText HybridView implementation - add Android Nitro module with Kotlin view, Fabric state updater, and JNI bridge - share component descriptor/state logic so Fabric can hydrate props on Android - update TypeScript entrypoint, codegen pipeline, and example app for Android support --- android/CMakeLists.txt | 43 ++ android/build.gradle | 148 +++++++ android/gradle.properties | 5 + android/src/main/AndroidManifest.xml | 2 + .../main/cpp/NitroTextRegisterProvider.cpp | 25 ++ .../main/cpp/NitroTextRegisterProvider.hpp | 13 + android/src/main/cpp/cpp-adapter.cpp | 8 + .../java/com/nitrotext/HybridNitroText.kt | 172 ++++++++ .../nitrotext/NitroTextImpl+LineHeightSpan.kt | 29 ++ .../main/java/com/nitrotext/NitroTextImpl.kt | 289 ++++++++++++ .../java/com/nitrotext/NitroTextPackage.kt | 27 ++ .../main/java/com/nitrotext/NitroTextView.kt | 48 ++ .../com/nitrotext/NitroTextViewManager.kt | 48 ++ .../NitroTextComponentDescriptor.cpp | 10 +- cpp/NitroTextComponentDescriptor.hpp | 30 ++ cpp/NitroTextShadowNode.cpp | 2 +- cpp/NitroTextShadowNode.hpp | 4 + example/App.tsx | 21 +- example/android/build.gradle | 2 +- example/ios/Podfile.lock | 4 +- ...ybridNitroTextComponent+ShadowOverride.mm} | 5 +- ios/NitroTextComponentDescriptor.hpp | 31 -- ios/NitroTextImpl.swift | 5 - nitro.json | 3 +- .../android/NitroText+autolinking.cmake | 83 ++++ .../android/NitroText+autolinking.gradle | 27 ++ .../generated/android/NitroTextOnLoad.cpp | 50 +++ .../generated/android/NitroTextOnLoad.hpp | 25 ++ .../android/c++/JDynamicTypeRamp.hpp | 86 ++++ .../generated/android/c++/JEllipsizeMode.hpp | 65 +++ nitrogen/generated/android/c++/JFontStyle.hpp | 62 +++ .../generated/android/c++/JFontWeight.hpp | 89 ++++ nitrogen/generated/android/c++/JFragment.hpp | 122 ++++++ nitrogen/generated/android/c++/JFunc_void.hpp | 74 ++++ .../c++/JFunc_void_TextLayoutEvent.hpp | 80 ++++ .../android/c++/JHybridNitroTextSpec.cpp | 411 ++++++++++++++++++ .../android/c++/JHybridNitroTextSpec.hpp | 121 ++++++ .../android/c++/JLineBreakStrategyIOS.hpp | 65 +++ nitrogen/generated/android/c++/JTextAlign.hpp | 68 +++ .../android/c++/JTextDecorationLine.hpp | 65 +++ .../android/c++/JTextDecorationStyle.hpp | 65 +++ .../generated/android/c++/JTextLayout.hpp | 85 ++++ .../android/c++/JTextLayoutEvent.hpp | 73 ++++ .../generated/android/c++/JTextTransform.hpp | 65 +++ .../views/JHybridNitroTextStateUpdater.cpp | 168 +++++++ .../views/JHybridNitroTextStateUpdater.hpp | 49 +++ .../nitro/nitrotext/DynamicTypeRamp.kt | 30 ++ .../margelo/nitro/nitrotext/EllipsizeMode.kt | 23 + .../com/margelo/nitro/nitrotext/FontStyle.kt | 22 + .../com/margelo/nitro/nitrotext/FontWeight.kt | 31 ++ .../com/margelo/nitro/nitrotext/Fragment.kt | 71 +++ .../com/margelo/nitro/nitrotext/Func_void.kt | 81 ++++ .../nitrotext/Func_void_TextLayoutEvent.kt | 81 ++++ .../nitro/nitrotext/HybridNitroTextSpec.kt | 255 +++++++++++ .../nitro/nitrotext/LineBreakStrategyIOS.kt | 23 + .../nitro/nitrotext/NitroTextOnLoad.kt | 35 ++ .../com/margelo/nitro/nitrotext/TextAlign.kt | 24 + .../nitro/nitrotext/TextDecorationLine.kt | 23 + .../nitro/nitrotext/TextDecorationStyle.kt | 23 + .../com/margelo/nitro/nitrotext/TextLayout.kt | 53 +++ .../nitro/nitrotext/TextLayoutEvent.kt | 29 ++ .../margelo/nitro/nitrotext/TextTransform.kt | 23 + .../nitrotext/views/HybridNitroTextManager.kt | 50 +++ .../views/HybridNitroTextStateUpdater.kt | 23 + .../c++/views/HybridNitroTextComponent.hpp | 38 -- package.json | 4 +- post-script.js | 32 ++ src/nitro-text.tsx | 6 +- src/specs/nitro-text.nitro.ts | 2 +- 69 files changed, 3861 insertions(+), 93 deletions(-) create mode 100644 android/CMakeLists.txt create mode 100644 android/build.gradle create mode 100644 android/gradle.properties create mode 100644 android/src/main/AndroidManifest.xml create mode 100644 android/src/main/cpp/NitroTextRegisterProvider.cpp create mode 100644 android/src/main/cpp/NitroTextRegisterProvider.hpp create mode 100644 android/src/main/cpp/cpp-adapter.cpp create mode 100755 android/src/main/java/com/nitrotext/HybridNitroText.kt create mode 100644 android/src/main/java/com/nitrotext/NitroTextImpl+LineHeightSpan.kt create mode 100644 android/src/main/java/com/nitrotext/NitroTextImpl.kt create mode 100755 android/src/main/java/com/nitrotext/NitroTextPackage.kt create mode 100644 android/src/main/java/com/nitrotext/NitroTextView.kt create mode 100644 android/src/main/java/com/nitrotext/NitroTextViewManager.kt rename ios/NitroTextComponentDescriptor.mm => cpp/NitroTextComponentDescriptor.cpp (90%) create mode 100644 cpp/NitroTextComponentDescriptor.hpp rename ios/{NitroTextShadowOverride.mm => HybridNitroTextComponent+ShadowOverride.mm} (93%) delete mode 100644 ios/NitroTextComponentDescriptor.hpp create mode 100644 nitrogen/generated/android/NitroText+autolinking.cmake create mode 100644 nitrogen/generated/android/NitroText+autolinking.gradle create mode 100644 nitrogen/generated/android/NitroTextOnLoad.cpp create mode 100644 nitrogen/generated/android/NitroTextOnLoad.hpp create mode 100644 nitrogen/generated/android/c++/JDynamicTypeRamp.hpp create mode 100644 nitrogen/generated/android/c++/JEllipsizeMode.hpp create mode 100644 nitrogen/generated/android/c++/JFontStyle.hpp create mode 100644 nitrogen/generated/android/c++/JFontWeight.hpp create mode 100644 nitrogen/generated/android/c++/JFragment.hpp create mode 100644 nitrogen/generated/android/c++/JFunc_void.hpp create mode 100644 nitrogen/generated/android/c++/JFunc_void_TextLayoutEvent.hpp create mode 100644 nitrogen/generated/android/c++/JHybridNitroTextSpec.cpp create mode 100644 nitrogen/generated/android/c++/JHybridNitroTextSpec.hpp create mode 100644 nitrogen/generated/android/c++/JLineBreakStrategyIOS.hpp create mode 100644 nitrogen/generated/android/c++/JTextAlign.hpp create mode 100644 nitrogen/generated/android/c++/JTextDecorationLine.hpp create mode 100644 nitrogen/generated/android/c++/JTextDecorationStyle.hpp create mode 100644 nitrogen/generated/android/c++/JTextLayout.hpp create mode 100644 nitrogen/generated/android/c++/JTextLayoutEvent.hpp create mode 100644 nitrogen/generated/android/c++/JTextTransform.hpp create mode 100644 nitrogen/generated/android/c++/views/JHybridNitroTextStateUpdater.cpp create mode 100644 nitrogen/generated/android/c++/views/JHybridNitroTextStateUpdater.hpp create mode 100644 nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/DynamicTypeRamp.kt create mode 100644 nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/EllipsizeMode.kt create mode 100644 nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/FontStyle.kt create mode 100644 nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/FontWeight.kt create mode 100644 nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/Fragment.kt create mode 100644 nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/Func_void.kt create mode 100644 nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/Func_void_TextLayoutEvent.kt create mode 100644 nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/HybridNitroTextSpec.kt create mode 100644 nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/LineBreakStrategyIOS.kt create mode 100644 nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/NitroTextOnLoad.kt create mode 100644 nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/TextAlign.kt create mode 100644 nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/TextDecorationLine.kt create mode 100644 nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/TextDecorationStyle.kt create mode 100644 nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/TextLayout.kt create mode 100644 nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/TextLayoutEvent.kt create mode 100644 nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/TextTransform.kt create mode 100644 nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/views/HybridNitroTextManager.kt create mode 100644 nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/views/HybridNitroTextStateUpdater.kt create mode 100644 post-script.js diff --git a/android/CMakeLists.txt b/android/CMakeLists.txt new file mode 100644 index 0000000..740acc8 --- /dev/null +++ b/android/CMakeLists.txt @@ -0,0 +1,43 @@ +project(NitroText) +cmake_minimum_required(VERSION 3.9.0) + +set (PACKAGE_NAME NitroText) +set (CMAKE_VERBOSE_MAKEFILE ON) +set (CMAKE_CXX_STANDARD 20) + +# Enable Raw Props parsing in react-native (for Nitro Views) +add_compile_options(-DRN_SERIALIZABLE_STATE=1) + +# Define C++ library and add all sources +add_library(${PACKAGE_NAME} SHARED + src/main/cpp/cpp-adapter.cpp +) + +# Add Nitrogen specs :) +include(${CMAKE_SOURCE_DIR}/../nitrogen/generated/android/NitroText+autolinking.cmake) + +# Set up local includes +include_directories( + "src/main/cpp" + "../cpp" +) + +# Add custom ShadowNode implementation +target_sources( + ${PACKAGE_NAME} PRIVATE + ../cpp/NitroTextShadowNode.cpp + ../cpp/NitroTextShadowNode.hpp + ../cpp/NitroTextComponentDescriptor.cpp + ../cpp/NitroTextComponentDescriptor.hpp + src/main/cpp/NitroTextRegisterProvider.cpp + .. +) + +find_library(LOG_LIB log) + +# Link all libraries together +target_link_libraries( + ${PACKAGE_NAME} + ${LOG_LIB} + android # <-- Android core +) diff --git a/android/build.gradle b/android/build.gradle new file mode 100644 index 0000000..a1abdce --- /dev/null +++ b/android/build.gradle @@ -0,0 +1,148 @@ +buildscript { + repositories { + google() + mavenCentral() + } + + dependencies { + classpath "com.android.tools.build:gradle:8.12.1" + } +} + +def reactNativeArchitectures() { + def value = rootProject.getProperties().get("reactNativeArchitectures") + return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"] +} + +def isNewArchitectureEnabled() { + return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true" +} + +apply plugin: "com.android.library" +apply plugin: 'org.jetbrains.kotlin.android' +apply from: '../nitrogen/generated/android/NitroText+autolinking.gradle' + +if (isNewArchitectureEnabled()) { + apply plugin: "com.facebook.react" +} + +def getExtOrDefault(name) { + return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties["NitroText_" + name] +} + +def getExtOrIntegerDefault(name) { + return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["NitroText_" + name]).toInteger() +} + +android { + namespace "com.nitrotext" + + ndkVersion getExtOrDefault("ndkVersion") + compileSdkVersion getExtOrIntegerDefault("compileSdkVersion") + + defaultConfig { + minSdkVersion getExtOrIntegerDefault("minSdkVersion") + targetSdkVersion getExtOrIntegerDefault("targetSdkVersion") + buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString() + + externalNativeBuild { + cmake { + cppFlags "-frtti -fexceptions -Wall -Wextra -fstack-protector-all" + arguments "-DANDROID_STL=c++_shared", "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON" + abiFilters (*reactNativeArchitectures()) + + buildTypes { + debug { + cppFlags "-O1 -g" + } + release { + cppFlags "-O2" + } + } + } + } + } + + externalNativeBuild { + cmake { + path "CMakeLists.txt" + } + } + + packagingOptions { + excludes = [ + "META-INF", + "META-INF/**", + "**/libc++_shared.so", + "**/libfbjni.so", + "**/libjsi.so", + "**/libfolly_json.so", + "**/libfolly_runtime.so", + "**/libglog.so", + "**/libhermes.so", + "**/libhermes-executor-debug.so", + "**/libhermes_executor.so", + "**/libreactnative.so", + "**/libreactnativejni.so", + "**/libturbomodulejsijni.so", + "**/libreact_nativemodule_core.so", + "**/libjscexecutor.so" + ] + } + + buildFeatures { + buildConfig true + prefab true + } + + buildTypes { + release { + minifyEnabled false + } + } + + lintOptions { + disable "GradleCompatible" + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + sourceSets { + main { + if (isNewArchitectureEnabled()) { + java.srcDirs += [ + // React Codegen files + "${project.buildDir}/generated/source/codegen/java" + ] + } + } + } +} + +repositories { + mavenCentral() + google() +} + + +dependencies { + // For < 0.71, this will be from the local maven repo + // For > 0.71, this will be replaced by `com.facebook.react:react-android:$version` by react gradle plugin + //noinspection GradleDynamicVersion + implementation "com.facebook.react:react-native:+" + + // Add a dependency on NitroModules + implementation project(":react-native-nitro-modules") + +} + +if (isNewArchitectureEnabled()) { + react { + jsRootDir = file("../src/") + libraryName = "NitroText" + codegenJavaPackageName = "com.nitrotext" + } +} \ No newline at end of file diff --git a/android/gradle.properties b/android/gradle.properties new file mode 100644 index 0000000..9b5affa --- /dev/null +++ b/android/gradle.properties @@ -0,0 +1,5 @@ +NitroText_kotlinVersion=2.0.21 +NitroText_minSdkVersion=29 +NitroText_targetSdkVersion=34 +NitroText_compileSdkVersion=34 +NitroText_ndkVersion=27.1.12297006 diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml new file mode 100644 index 0000000..a2f47b6 --- /dev/null +++ b/android/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + diff --git a/android/src/main/cpp/NitroTextRegisterProvider.cpp b/android/src/main/cpp/NitroTextRegisterProvider.cpp new file mode 100644 index 0000000..2a7bf57 --- /dev/null +++ b/android/src/main/cpp/NitroTextRegisterProvider.cpp @@ -0,0 +1,25 @@ +// +// NitroTextRegisterProvider.cpp +// Registers custom ComponentDescriptor for NitroText on Android +// + +#include "NitroTextRegisterProvider.hpp" + +#include +#include +#include "../../../cpp/NitroTextComponentDescriptor.hpp" + +namespace margelo::nitro::nitrotext { + +// Call this from JNI_OnLoad after nitrogen initialization +void registerNitroTextComponentDescriptor() { + using namespace facebook::react; + using margelo::nitro::nitrotext::views::NitroTextComponentDescriptor; + + auto provider = concreteComponentDescriptorProvider(); + auto providerRegistry = CoreComponentsRegistry::sharedProviderRegistry(); + // Add/override provider for component name "NitroText" (HybridNitroTextComponentName) + providerRegistry->add(provider); +} + +} // namespace margelo::nitro::nitrotext diff --git a/android/src/main/cpp/NitroTextRegisterProvider.hpp b/android/src/main/cpp/NitroTextRegisterProvider.hpp new file mode 100644 index 0000000..c766bad --- /dev/null +++ b/android/src/main/cpp/NitroTextRegisterProvider.hpp @@ -0,0 +1,13 @@ +// +// NitroTextRegisterProvider.hpp +// Declares helper to register custom ComponentDescriptor +// + +#pragma once + +namespace margelo::nitro::nitrotext { + +void registerNitroTextComponentDescriptor(); + +} + diff --git a/android/src/main/cpp/cpp-adapter.cpp b/android/src/main/cpp/cpp-adapter.cpp new file mode 100644 index 0000000..8701791 --- /dev/null +++ b/android/src/main/cpp/cpp-adapter.cpp @@ -0,0 +1,8 @@ +#include +#include "NitroTextOnLoad.hpp" +#include "NitroTextRegisterProvider.hpp" + +JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) { + margelo::nitro::nitrotext::registerNitroTextComponentDescriptor(); + return margelo::nitro::nitrotext::initialize(vm); +} diff --git a/android/src/main/java/com/nitrotext/HybridNitroText.kt b/android/src/main/java/com/nitrotext/HybridNitroText.kt new file mode 100755 index 0000000..11e2802 --- /dev/null +++ b/android/src/main/java/com/nitrotext/HybridNitroText.kt @@ -0,0 +1,172 @@ +package com.nitrotext + +import android.os.Build +import android.view.View +import androidx.annotation.Keep +import androidx.annotation.RequiresApi +import com.facebook.proguard.annotations.DoNotStrip +import com.facebook.react.uimanager.ThemedReactContext +import com.margelo.nitro.nitrotext.* + +@Keep +@DoNotStrip +class HybridNitroText(val context: ThemedReactContext) : HybridNitroTextSpec() { + override val view: NitroTextView = NitroTextView(context) + private val impl = NitroTextImpl(view.textView) + + override var fragments: Array? + get() = null + set(value) { + impl.setFragments(value) + } + + override var selectable: Boolean? + get() = null + set(value) { + impl.setSelectable(value) + } + + override var allowFontScaling: Boolean? + get() = null + set(value) { + impl.setAllowFontScaling(value) + } + + override var ellipsizeMode: EllipsizeMode? + get() = null + set(value) { + impl.setEllipsizeMode(value) + } + + override var numberOfLines: Double? + get() = null + set(value) { + impl.setNumberOfLines(value) + } + + override var lineBreakStrategyIOS: LineBreakStrategyIOS? + get() = null + set(value) { + // iOS only + } + + override var dynamicTypeRamp: DynamicTypeRamp? + get() = null + set(value) { + // iOS only + } + + override var maxFontSizeMultiplier: Double? + get() = null + set(value) { + impl.setMaxFontSizeMultiplier(value) + } + + override var adjustsFontSizeToFit: Boolean? + get() = null + set(value) { + // TODO: Implement adjustsFontSizeToFit + } + + override var minimumFontScale: Double? + get() = null + set(value) { value } + + override var onTextLayout: ((TextLayoutEvent) -> Unit)? + get() = null + set(value) { + // TODO: Implement onTextLayout + } + + override var onPress: (() -> Unit)? + get() = null + set(value) { + // TODO: Implement onPress + } + + override var onPressIn: (() -> Unit)? + get() = null + set(value) { + // TODO: Implement onPressIn + } + + override var onPressOut: (() -> Unit)? + get() = null + set(value) { + // TODO: Implement onPressOut + } + + override var text: String? + get() = null + set(value) { + impl.setText(value) + } + + override var selectionColor: String? + get() = null + set(value) { + impl.setSelectionColor(value) + } + + override var fontSize: Double? + get() = null + set(value) { impl.setFontSize(value) } + + override var fontWeight: FontWeight? + get() = null + set(value) { impl.setFontWeight(value) } + + override var fontColor: String? + get() = null + set(value) { + impl.setFontColor(value) + } + + override var fragmentBackgroundColor: String? + get() = null + set(value) { + // TODO: Implement fragmentBackgroundColor + } + + override var fontStyle: FontStyle? + get() = null + set(value) { impl.setFontStyle(value) } + + override var fontFamily: String? + get() = null + set(value) { impl.setFontFamily(value) } + + override var lineHeight: Double? + get() = null + set(value) { impl.setLineHeight(value) } + + override var letterSpacing: Double? + get() = null + set(value) { impl.setLetterSpacing(value) } + + override var textAlign: TextAlign? + get() = null + set(value) { impl.setTextAlign(value) } + + override var textTransform: TextTransform? + get() = null + set(value) { impl.setTextTransform(value) } + + override var textDecorationLine: TextDecorationLine? + get() = null + set(value) { impl.setTextDecorationLine(value) } + + override var textDecorationColor: String? + get() = null + set(value) {impl.setTextDecorationColor(value) } + + override var textDecorationStyle: TextDecorationStyle? + get() = null + set(value) { impl.setTextDecorationStyle(value) } + + override fun afterUpdate() { + impl.commit() + view.requestLayout() + view.invalidate() + } +} diff --git a/android/src/main/java/com/nitrotext/NitroTextImpl+LineHeightSpan.kt b/android/src/main/java/com/nitrotext/NitroTextImpl+LineHeightSpan.kt new file mode 100644 index 0000000..15d1fbd --- /dev/null +++ b/android/src/main/java/com/nitrotext/NitroTextImpl+LineHeightSpan.kt @@ -0,0 +1,29 @@ +package com.nitrotext + +import android.graphics.Paint +import android.text.style.LineHeightSpan +import kotlin.math.ceil +import kotlin.math.floor + +/** + * Matches React Native's CustomLineHeightSpan so lineHeight behaves the same as . + */ +class NitroTextImplLineHeightSpan(heightPx: Float) : LineHeightSpan { + private val lineHeight: Int = ceil(heightPx.toDouble()).toInt() + + override fun chooseHeight( + text: CharSequence, + start: Int, + end: Int, + spanstartv: Int, + v: Int, + fm: Paint.FontMetricsInt + ) { + val leading = lineHeight - ((-fm.ascent) + fm.descent) + val halfLeading = leading / 2f + fm.ascent -= ceil(halfLeading.toDouble()).toInt() + fm.descent += floor(halfLeading.toDouble()).toInt() + if (start == 0) fm.top = fm.ascent + if (end == text.length) fm.bottom = fm.descent + } +} diff --git a/android/src/main/java/com/nitrotext/NitroTextImpl.kt b/android/src/main/java/com/nitrotext/NitroTextImpl.kt new file mode 100644 index 0000000..902f8e2 --- /dev/null +++ b/android/src/main/java/com/nitrotext/NitroTextImpl.kt @@ -0,0 +1,289 @@ +package com.nitrotext + +import android.graphics.Typeface +import android.text.Spannable +import android.text.SpannableStringBuilder +import android.text.TextUtils +import android.text.style.AbsoluteSizeSpan +import android.text.style.BackgroundColorSpan +import android.text.style.ForegroundColorSpan +import android.text.style.StrikethroughSpan +import android.text.style.StyleSpan +import android.text.style.TypefaceSpan +import android.text.style.UnderlineSpan +import android.util.Log +import android.view.Gravity +import androidx.appcompat.widget.AppCompatTextView +import com.facebook.react.uimanager.PixelUtil +import com.margelo.nitro.nitrotext.* +import androidx.core.graphics.toColorInt + +class NitroTextImpl(private val view: AppCompatTextView) { + // Stored props + private var fragments: Array? = null + private var text: String? = null + + private var selectable: Boolean? = null + private var selectionColor: String? = null + private var numberOfLines: Double? = null + private var ellipsizeMode: EllipsizeMode? = null + private var allowFontScaling: Boolean = true + private var maxFontSizeMultiplier: Double? = null + + // Top-level styling (applied when using simple text) + private var fontSize: Double? = null + private var fontWeight: FontWeight? = null + private var fontColor: String? = null + private var fontStyle: FontStyle? = null + private var fontFamily: String? = null + private var letterSpacing: Double? = null + private var lineHeight: Double? = null + private var textAlign: TextAlign? = null + private var textTransform: TextTransform? = null + private var textDecorationLine: TextDecorationLine? = null + private var textDecorationColor: String? = null + private var textDecorationStyle: TextDecorationStyle? = null + + fun commit() { + // Reset typography to avoid stale values from recycled views + view.setLineSpacing(0f, 1f) + view.letterSpacing = 0f + applySelectable() + applySelectionColor() + applyLinesAndEllipsize() + applyAlignment() + + val frags = fragments + if (!frags.isNullOrEmpty()) { + applyFragments(frags) + } else { + applySimpleText() + } + + applyLetterSpacing() + } + + // Setters + fun setFragments(value: Array?) { fragments = value } + fun setText(value: String?) { text = value } + + fun setSelectable(value: Boolean?) { selectable = value } + fun setSelectionColor(value: String?) { selectionColor = value } + fun setNumberOfLines(value: Double?) { numberOfLines = value } + fun setEllipsizeMode(value: EllipsizeMode?) { ellipsizeMode = value } + fun setAllowFontScaling(value: Boolean?) { allowFontScaling = value ?: true } + fun setMaxFontSizeMultiplier(value: Double?) { maxFontSizeMultiplier = value } + + fun setFontSize(value: Double?) { fontSize = value } + fun setFontWeight(value: FontWeight?) { fontWeight = value } + fun setFontColor(value: String?) { fontColor = value } + fun setFontStyle(value: FontStyle?) { fontStyle = value } + fun setFontFamily(value: String?) { fontFamily = value } + fun setLetterSpacing(value: Double?) { letterSpacing = value } + fun setLineHeight(value: Double?) { lineHeight = value } + fun setTextAlign(value: TextAlign?) { textAlign = value } + fun setTextTransform(value: TextTransform?) { textTransform = value } + fun setTextDecorationLine(value: TextDecorationLine?) { textDecorationLine = value } + fun setTextDecorationColor(value: String?) { textDecorationColor = value } + fun setTextDecorationStyle(value: TextDecorationStyle?) { textDecorationStyle = value } + + // Apply helpers + private fun applySelectable() { + selectable?.let { view.setTextIsSelectable(it) } + } + + private fun applySelectionColor() { + selectionColor?.let { colorString -> + parseColorSafe(colorString)?.let { view.highlightColor = it } + } + } + + private fun applyLinesAndEllipsize() { + val lines = numberOfLines?.toInt() ?: Int.MAX_VALUE + view.maxLines = if (lines <= 0) Int.MAX_VALUE else lines + view.isSingleLine = (lines == 1) + + view.ellipsize = when (ellipsizeMode) { + EllipsizeMode.HEAD -> TextUtils.TruncateAt.START + EllipsizeMode.MIDDLE -> TextUtils.TruncateAt.MIDDLE + EllipsizeMode.TAIL -> TextUtils.TruncateAt.END + EllipsizeMode.CLIP -> null + else -> TextUtils.TruncateAt.END + } + } + + private fun applyLetterSpacing() { + letterSpacing?.let { spacingPx -> + val spacingPxFloat = if (allowFontScaling) { + PixelUtil.toPixelFromSP(spacingPx.toFloat(), maxFontScale()) + } else { + PixelUtil.toPixelFromDIP(spacingPx.toFloat()) + } + val textSizePx = view.textSize + if (textSizePx > 0f) { + val em = spacingPxFloat / textSizePx + view.letterSpacing = em + } + } + } + + private fun applyAlignment() { + when (textAlign) { + TextAlign.LEFT -> view.gravity = Gravity.START or Gravity.CENTER_VERTICAL + TextAlign.RIGHT -> view.gravity = Gravity.END or Gravity.CENTER_VERTICAL + TextAlign.CENTER -> view.gravity = Gravity.CENTER + TextAlign.JUSTIFY, TextAlign.AUTO, null -> Unit + } + } + + private fun applySimpleText() { + val content = transformText(text, textTransform) + val lineHeightPx = resolveLineHeight(lineHeight) + if (content != null) { + val spansRequired = textDecorationLine != null || lineHeightPx != null + if (spansRequired) { + val builder = SpannableStringBuilder(content) + applyDecorationSpans(builder, 0, builder.length, textDecorationLine) + lineHeightPx?.let { applyLineHeightSpan(builder, 0, builder.length, it) } + view.text = builder + } else { + view.text = content + } + } else { + view.text = "" + } + + fontColor?.let { parseColorSafe(it)?.let(view::setTextColor) } + fontSize?.let { + if (allowFontScaling) { + view.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, it.toFloat()) + } else { + val px = PixelUtil.toPixelFromDIP(it.toFloat()) + view.setTextSize(android.util.TypedValue.COMPLEX_UNIT_PX, px) + } + } + + val style = combineStyle(fontWeight, fontStyle) + if (style != Typeface.NORMAL) view.setTypeface(view.typeface, style) + } + + private fun applyFragments(fragments: Array) { + val builder = SpannableStringBuilder() + val containerLineHeightPx = resolveLineHeight(lineHeight) + var start: Int + for (frag in fragments) { + val fragText = transformText(frag.text, frag.textTransform) ?: "" + start = builder.length + builder.append(fragText) + val end = builder.length + if (start == end) continue + + frag.fontColor?.let { parseColorSafe(it)?.let { c -> + builder.setSpan(ForegroundColorSpan(c), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + }} + frag.fragmentBackgroundColor?.let { parseColorSafe(it)?.let { c -> + builder.setSpan(BackgroundColorSpan(c), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + }} + // Font Size in SP + frag.fontSize?.let { sz -> + builder.setSpan(AbsoluteSizeSpan(sz.toInt(), true), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + } + // Font Weight/Style + val style = combineStyle(frag.fontWeight, frag.fontStyle) + if (style != Typeface.NORMAL) { + builder.setSpan(StyleSpan(style), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + } + // Font Family + frag.fontFamily?.let { + builder.setSpan(TypefaceSpan(it), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + } + // Decorations + applyDecorationSpans(builder, start, end, frag.textDecorationLine) + frag.lineHeight?.let { lh -> + resolveLineHeight(lh)?.let { applyLineHeightSpan(builder, start, end, it) } + } + } + containerLineHeightPx?.let { applyLineHeightSpan(builder, 0, builder.length, it) } + view.text = builder + + // Apply default text color for runs without explicit color + fontColor?.let { parseColorSafe(it)?.let(view::setTextColor) } + } + + private fun applyDecorationSpans(builder: SpannableStringBuilder, start: Int, end: Int, line: TextDecorationLine?) { + when (line) { + TextDecorationLine.UNDERLINE -> builder.setSpan(UnderlineSpan(), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + TextDecorationLine.LINE_THROUGH -> builder.setSpan(StrikethroughSpan(), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + TextDecorationLine.UNDERLINE_LINE_THROUGH -> { + builder.setSpan(UnderlineSpan(), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + builder.setSpan(StrikethroughSpan(), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + } + else -> Unit + } + } + + private fun combineStyle(weight: FontWeight?, style: FontStyle?): Int { + val isBold = when (weight) { + FontWeight.BOLD, FontWeight.SEMIBOLD, FontWeight.HEAVY, FontWeight.BLACK, FontWeight.CONDENSEDBOLD -> true + else -> false + } + val isItalic = when (style) { + FontStyle.ITALIC, FontStyle.OBLIQUE -> true + else -> false + } + return when { + isBold && isItalic -> Typeface.BOLD_ITALIC + isBold -> Typeface.BOLD + isItalic -> Typeface.ITALIC + else -> Typeface.NORMAL + } + } + + private fun transformText(text: String?, transform: TextTransform?): String? { + if (text == null) return null + return when (transform) { + TextTransform.UPPERCASE -> text.uppercase() + TextTransform.LOWERCASE -> text.lowercase() + TextTransform.CAPITALIZE -> text.split(" ").joinToString(" ") { it.replaceFirstChar { c -> c.uppercase() } } + else -> text + } + } + + private fun parseColorSafe(str: String): Int? = try { + str.toColorInt() + } catch (_: Throwable) { null } + + private fun resolveLineHeight(value: Double?): Float? { + val raw = value ?: return null + val px = if (allowFontScaling) { + PixelUtil.toPixelFromSP(raw.toFloat(), maxFontScale()) + } else { + PixelUtil.toPixelFromDIP(raw.toFloat()) + } + return px.takeIf { it > 0f } + } + + private fun maxFontScale(): Float { + val multiplier = maxFontSizeMultiplier + return if (multiplier != null && multiplier >= 1.0) multiplier.toFloat() else Float.NaN + } + + private fun applyLineHeightSpan( + builder: SpannableStringBuilder, + start: Int, + end: Int, + lineHeightPx: Float + ) { + if (start >= end) return + val flags = if (start == 0) { + Spannable.SPAN_INCLUSIVE_INCLUSIVE + } else { + Spannable.SPAN_EXCLUSIVE_INCLUSIVE + } + builder.setSpan(NitroTextImplLineHeightSpan(lineHeightPx), start, end, flags) + } + + companion object { + private const val TAG = "NitroTextImpl" + } +} diff --git a/android/src/main/java/com/nitrotext/NitroTextPackage.kt b/android/src/main/java/com/nitrotext/NitroTextPackage.kt new file mode 100755 index 0000000..75aa903 --- /dev/null +++ b/android/src/main/java/com/nitrotext/NitroTextPackage.kt @@ -0,0 +1,27 @@ +package com.nitrotext + +import com.facebook.react.bridge.NativeModule +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.uimanager.ViewManager +import com.facebook.react.TurboReactPackage +import com.facebook.react.module.model.ReactModuleInfoProvider +import com.margelo.nitro.nitrotext.NitroTextOnLoad +import java.util.HashMap + +class NitroTextPackage : TurboReactPackage() { + override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? = null + + override fun getReactModuleInfoProvider(): ReactModuleInfoProvider = ReactModuleInfoProvider { HashMap() } + + override fun createViewManagers(reactContext: ReactApplicationContext): List> { + val viewManagers = ArrayList>() + viewManagers.add(NitroTextViewManager()); + return viewManagers; + } + + companion object { + init { + NitroTextOnLoad.initializeNative() + } + } +} diff --git a/android/src/main/java/com/nitrotext/NitroTextView.kt b/android/src/main/java/com/nitrotext/NitroTextView.kt new file mode 100644 index 0000000..7847fa6 --- /dev/null +++ b/android/src/main/java/com/nitrotext/NitroTextView.kt @@ -0,0 +1,48 @@ +package com.nitrotext + +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.text.LineBreaker +import android.text.Layout +import android.view.View +import androidx.appcompat.widget.AppCompatTextView +import com.facebook.react.views.view.ReactViewGroup + +@SuppressLint("ViewConstructor") +class NitroTextView(ctx: Context) : ReactViewGroup(ctx){ + val textView = AppCompatTextView(ctx).apply { + includeFontPadding = false + minWidth = 0; minHeight = 0 + breakStrategy = LineBreaker.BREAK_STRATEGY_HIGH_QUALITY + hyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NORMAL + setHorizontallyScrolling(false) + } + + init { + // Fill the container; borders/radius are applied to this container by RN. + addView( + textView, + LayoutParams( + LayoutParams.MATCH_PARENT, + LayoutParams.MATCH_PARENT + ) + ) + } + + override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { + val childLeft = paddingLeft + val childTop = paddingTop + val childRight = measuredWidth - paddingRight + val childBottom = measuredHeight - paddingBottom + textView.layout(childLeft, childTop, childRight, childBottom) + } + + // Block adding of any other children to this container (we only have a single TextView) + override fun addView(child: View?, index: Int) { + if (child === textView) super.addView(child, index) + } + + override fun addView(child: View?, index: Int, params: LayoutParams?) { + if (child === textView) super.addView(child, index, params) + } +} diff --git a/android/src/main/java/com/nitrotext/NitroTextViewManager.kt b/android/src/main/java/com/nitrotext/NitroTextViewManager.kt new file mode 100644 index 0000000..8e2687b --- /dev/null +++ b/android/src/main/java/com/nitrotext/NitroTextViewManager.kt @@ -0,0 +1,48 @@ +package com.nitrotext + +import com.facebook.react.uimanager.ReactStylesDiffMap +import com.facebook.react.uimanager.StateWrapper +import com.facebook.react.uimanager.ThemedReactContext +import com.facebook.react.views.view.ReactViewGroup +import com.facebook.react.views.view.ReactViewManager +import com.margelo.nitro.nitrotext.views.HybridNitroTextStateUpdater + + +/** + * Represents the React Native `ViewManager` for the "NitroText" Nitro HybridView. + */ +open class NitroTextViewManager: ReactViewManager() { + private val views = hashMapOf() + + override fun getName(): String { + return "NitroText" + } + + override fun createViewInstance(context: ThemedReactContext): ReactViewGroup { + val hybridView = HybridNitroText(context) + val view = hybridView.view + views[view] = hybridView + return view + } + + override fun setPadding(view: ReactViewGroup?, left: Int, top: Int, right: Int, bottom: Int) { + view?.setPadding(left, top, right, bottom) + } + + override fun onDropViewInstance(view: ReactViewGroup) { + super.onDropViewInstance(view) + views.remove(view) + } + + override fun updateState(view: ReactViewGroup, props: ReactStylesDiffMap, stateWrapper: StateWrapper): Any? { + val hybridView = views[view] ?: throw Error("Couldn't find view $view in local views table!") + + // 1. Update each prop individually + hybridView.beforeUpdate() + HybridNitroTextStateUpdater.updateViewProps(hybridView, stateWrapper) + hybridView.afterUpdate() + + // 2. Continue in base View props + return super.updateState(view, props, stateWrapper) + } +} diff --git a/ios/NitroTextComponentDescriptor.mm b/cpp/NitroTextComponentDescriptor.cpp similarity index 90% rename from ios/NitroTextComponentDescriptor.mm rename to cpp/NitroTextComponentDescriptor.cpp index e7402d7..3314257 100644 --- a/ios/NitroTextComponentDescriptor.mm +++ b/cpp/NitroTextComponentDescriptor.cpp @@ -1,10 +1,11 @@ // -// NitroTextComponentDescriptor.mm -// Implementation for custom ComponentDescriptor +// NitroTextComponentDescriptor.cpp +// Shared implementation for custom ComponentDescriptor // -#import "NitroTextComponentDescriptor.hpp" -#import +#include "NitroTextComponentDescriptor.hpp" + +#include using namespace facebook; using namespace margelo::nitro::nitrotext::views; @@ -38,6 +39,7 @@ // Inject TextLayoutManager so measurement works on Fabric (iOS/macOS/etc.). // Construct directly with the descriptor's ContextContainer. + if (auto contextContainer = this->getContextContainer()) { auto textLayoutManager = std::make_shared(contextContainer); concreteShadowNode.setTextLayoutManager(textLayoutManager); diff --git a/cpp/NitroTextComponentDescriptor.hpp b/cpp/NitroTextComponentDescriptor.hpp new file mode 100644 index 0000000..4a7dd97 --- /dev/null +++ b/cpp/NitroTextComponentDescriptor.hpp @@ -0,0 +1,30 @@ +// +// NitroTextComponentDescriptor.hpp (shared) +// Custom, non-generated ComponentDescriptor for NitroText +// + +#pragma once + +#include "NitroTextShadowNode.hpp" +#include + +namespace margelo::nitro::nitrotext::views { + /** + * The Component Descriptor for the "NitroText" View. + */ + class NitroTextComponentDescriptor final: public react::ConcreteComponentDescriptor { + public: + NitroTextComponentDescriptor(const react::ComponentDescriptorParameters& parameters); + + public: + /** + * A faster path for cloning props - reuses the caching logic from `HybridNitroTextProps`. + */ + std::shared_ptr cloneProps(const react::PropsParserContext& context, + const std::shared_ptr& props, + react::RawProps rawProps) const override; + + void adopt(react::ShadowNode& shadowNode) const override; + }; + + } // namespace margelo::nitro::nitrotext::views diff --git a/cpp/NitroTextShadowNode.cpp b/cpp/NitroTextShadowNode.cpp index 3c999bc..8b9e795 100644 --- a/cpp/NitroTextShadowNode.cpp +++ b/cpp/NitroTextShadowNode.cpp @@ -412,7 +412,7 @@ react::Size NitroTextShadowNode::measureContent( react::TextLayoutContext textLayoutContext{ .pointScaleFactor = layoutContext.pointScaleFactor, // TODO: investigate why surfaceId is not working for react-native <= 0.79 -// .surfaceId = this->getSurfaceId(), + .surfaceId = this->getSurfaceId(), }; const auto measurement = textLayoutManager_->measure( diff --git a/cpp/NitroTextShadowNode.hpp b/cpp/NitroTextShadowNode.hpp index 68cd7ef..34a0f5a 100644 --- a/cpp/NitroTextShadowNode.hpp +++ b/cpp/NitroTextShadowNode.hpp @@ -5,7 +5,11 @@ #pragma once +#ifdef ANDROID +#include "views/HybridNitroTextComponent.hpp" +#else #include "HybridNitroTextComponent.hpp" +#endif #include #include diff --git a/example/App.tsx b/example/App.tsx index 8995031..fe84fa4 100644 --- a/example/App.tsx +++ b/example/App.tsx @@ -34,12 +34,15 @@ export default function App() { High-performance selectable text with native rendering + + High-performance selectable text with native rendering + {/* Basic Usage */} Basic Usage - + This is a simple NitroText component with native performance. Try selecting this text to see the smooth selection behavior! @@ -48,7 +51,7 @@ export default function App() { {/* Nested NitroText wth numberOfLines (does not work currently it only renders the first line nested text doesn't render) */} - Nested NitroText with numberOfLines + Nested NitroText with numberOfLines (NitroText) This is a simple NitroText component with native performance.{' '} @@ -57,6 +60,17 @@ export default function App() { + + + Nested NitroText with numberOfLines (RN Text) + + + This is a simple NitroText component with native performance.{' '} + + Try selecting this text to see the smooth selection behavior! + + + {/* Rich Text Formatting */} @@ -270,6 +284,9 @@ const styles = StyleSheet.create({ textAlign: 'center', alignSelf: 'flex-start', lineHeight: 24, + borderWidth: 1, + borderColor: 'blue', + width: '100%', }, sectionTitle: { fontSize: 24, diff --git a/example/android/build.gradle b/example/android/build.gradle index dad99b0..08d3659 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -1,7 +1,7 @@ buildscript { ext { buildToolsVersion = "36.0.0" - minSdkVersion = 24 + minSdkVersion = 29 compileSdkVersion = 36 targetSdkVersion = 36 ndkVersion = "27.1.12297006" diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 960a603..7b6121e 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -37,7 +37,7 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - NitroText (1.0.2): + - NitroText (1.0.3): - boost - DoubleConversion - fast_float @@ -2644,7 +2644,7 @@ SPEC CHECKSUMS: glog: 5683914934d5b6e4240e497e0f4a3b42d1854183 hermes-engine: 35c763d57c9832d0eef764316ca1c4d043581394 NitroModules: 7d693306799405ca141ef5c24efc0936f20a09c0 - NitroText: 1f69b7c0c89d9f7cbf35fb8cf114dc6ecf8981a6 + NitroText: c95604214333634a6a083ac7211b238686dbd628 RCT-Folly: 846fda9475e61ec7bcbf8a3fe81edfcaeb090669 RCTDeprecation: c0ed3249a97243002615517dff789bf4666cf585 RCTRequired: 58719f5124f9267b5f9649c08bf23d9aea845b23 diff --git a/ios/NitroTextShadowOverride.mm b/ios/HybridNitroTextComponent+ShadowOverride.mm similarity index 93% rename from ios/NitroTextShadowOverride.mm rename to ios/HybridNitroTextComponent+ShadowOverride.mm index 800f38e..b52b5d6 100644 --- a/ios/NitroTextShadowOverride.mm +++ b/ios/HybridNitroTextComponent+ShadowOverride.mm @@ -1,5 +1,5 @@ // -// NitroTextShadowOverride.mm +// HybridNitroTextComponent+ShadowOverride.mm // Override only the ShadowNode/Descriptor for the generated view class // without introducing a new ComponentView class. // @@ -9,7 +9,7 @@ #import #import -#import "NitroTextComponentDescriptor.hpp" +#import "../cpp/NitroTextComponentDescriptor.hpp" // Forward-declare the generated view class; we don't import generated headers here. @interface HybridNitroTextComponent : RCTViewComponentView @@ -39,4 +39,3 @@ + (void)load } @end - diff --git a/ios/NitroTextComponentDescriptor.hpp b/ios/NitroTextComponentDescriptor.hpp deleted file mode 100644 index dda54b1..0000000 --- a/ios/NitroTextComponentDescriptor.hpp +++ /dev/null @@ -1,31 +0,0 @@ -// -// NitroTextComponentDescriptor.hpp -// Custom, non-generated ComponentDescriptor for NitroText -// - -#pragma once - -#include "../cpp/NitroTextShadowNode.hpp" -#include - -namespace margelo::nitro::nitrotext::views { - - /** - * The Component Descriptor for the "NitroText" View. - */ - class NitroTextComponentDescriptor final: public react::ConcreteComponentDescriptor { - public: - NitroTextComponentDescriptor(const react::ComponentDescriptorParameters& parameters); - - public: - /** - * A faster path for cloning props - reuses the caching logic from `HybridNitroTextProps`. - */ - std::shared_ptr cloneProps(const react::PropsParserContext& context, - const std::shared_ptr& props, - react::RawProps rawProps) const override; - - void adopt(react::ShadowNode& shadowNode) const override; - }; - -} // namespace margelo::nitro::nitrotext::views diff --git a/ios/NitroTextImpl.swift b/ios/NitroTextImpl.swift index 85f1341..943d886 100644 --- a/ios/NitroTextImpl.swift +++ b/ios/NitroTextImpl.swift @@ -165,11 +165,6 @@ final class NitroTextImpl { } } - func setPlainText(_ value: String?) { - let attributed = NSAttributedString(string: value ?? "") - setText(attributed) - } - func setTextAlign(_ align: TextAlign?) { switch align { case .some(.center): currentTextAlignment = .center diff --git a/nitro.json b/nitro.json index 34222e1..8ce4525 100644 --- a/nitro.json +++ b/nitro.json @@ -14,7 +14,8 @@ }, "autolinking": { "NitroText": { - "swift": "HybridNitroText" + "swift": "HybridNitroText", + "kotlin": "HybridNitroText" } }, "ignorePaths": [ diff --git a/nitrogen/generated/android/NitroText+autolinking.cmake b/nitrogen/generated/android/NitroText+autolinking.cmake new file mode 100644 index 0000000..f9f7c17 --- /dev/null +++ b/nitrogen/generated/android/NitroText+autolinking.cmake @@ -0,0 +1,83 @@ +# +# NitroText+autolinking.cmake +# This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +# https://github.com/mrousavy/nitro +# Copyright © 2025 Marc Rousavy @ Margelo +# + +# This is a CMake file that adds all files generated by Nitrogen +# to the current CMake project. +# +# To use it, add this to your CMakeLists.txt: +# ```cmake +# include(${CMAKE_SOURCE_DIR}/../nitrogen/generated/android/NitroText+autolinking.cmake) +# ``` + +# Define a flag to check if we are building properly +add_definitions(-DBUILDING_NITROTEXT_WITH_GENERATED_CMAKE_PROJECT) + +# Enable Raw Props parsing in react-native (for Nitro Views) +add_definitions(-DRN_SERIALIZABLE_STATE) + +# Add all headers that were generated by Nitrogen +include_directories( + "../nitrogen/generated/shared/c++" + "../nitrogen/generated/android/c++" + "../nitrogen/generated/android/" +) + +# Add all .cpp sources that were generated by Nitrogen +target_sources( + # CMake project name (Android C++ library name) + NitroText PRIVATE + # Autolinking Setup + ../nitrogen/generated/android/NitroTextOnLoad.cpp + # Shared Nitrogen C++ sources + ../nitrogen/generated/shared/c++/HybridNitroTextSpec.cpp + ../nitrogen/generated/shared/c++/views/HybridNitroTextComponent.cpp + # Android-specific Nitrogen C++ sources + ../nitrogen/generated/android/c++/JHybridNitroTextSpec.cpp + ../nitrogen/generated/android/c++/views/JHybridNitroTextStateUpdater.cpp +) + +# From node_modules/react-native/ReactAndroid/cmake-utils/folly-flags.cmake +# Used in node_modules/react-native/ReactAndroid/cmake-utils/ReactNative-application.cmake +target_compile_definitions( + NitroText PRIVATE + -DFOLLY_NO_CONFIG=1 + -DFOLLY_HAVE_CLOCK_GETTIME=1 + -DFOLLY_USE_LIBCPP=1 + -DFOLLY_CFG_NO_COROUTINES=1 + -DFOLLY_MOBILE=1 + -DFOLLY_HAVE_RECVMMSG=1 + -DFOLLY_HAVE_PTHREAD=1 + # Once we target android-23 above, we can comment + # the following line. NDK uses GNU style stderror_r() after API 23. + -DFOLLY_HAVE_XSI_STRERROR_R=1 +) + +# Add all libraries required by the generated specs +find_package(fbjni REQUIRED) # <-- Used for communication between Java <-> C++ +find_package(ReactAndroid REQUIRED) # <-- Used to set up React Native bindings (e.g. CallInvoker/TurboModule) +find_package(react-native-nitro-modules REQUIRED) # <-- Used to create all HybridObjects and use the Nitro core library + +# Link all libraries together +target_link_libraries( + NitroText + fbjni::fbjni # <-- Facebook C++ JNI helpers + ReactAndroid::jsi # <-- RN: JSI + react-native-nitro-modules::NitroModules # <-- NitroModules Core :) +) + +# Link react-native (different prefab between RN 0.75 and RN 0.76) +if(ReactAndroid_VERSION_MINOR GREATER_EQUAL 76) + target_link_libraries( + NitroText + ReactAndroid::reactnative # <-- RN: Native Modules umbrella prefab + ) +else() + target_link_libraries( + NitroText + ReactAndroid::react_nativemodule_core # <-- RN: TurboModules Core + ) +endif() diff --git a/nitrogen/generated/android/NitroText+autolinking.gradle b/nitrogen/generated/android/NitroText+autolinking.gradle new file mode 100644 index 0000000..3ad0a33 --- /dev/null +++ b/nitrogen/generated/android/NitroText+autolinking.gradle @@ -0,0 +1,27 @@ +/// +/// NitroText+autolinking.gradle +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +/// This is a Gradle file that adds all files generated by Nitrogen +/// to the current Gradle project. +/// +/// To use it, add this to your build.gradle: +/// ```gradle +/// apply from: '../nitrogen/generated/android/NitroText+autolinking.gradle' +/// ``` + +logger.warn("[NitroModules] 🔥 NitroText is boosted by nitro!") + +android { + sourceSets { + main { + java.srcDirs += [ + // Nitrogen files + "${project.projectDir}/../nitrogen/generated/android/kotlin" + ] + } + } +} diff --git a/nitrogen/generated/android/NitroTextOnLoad.cpp b/nitrogen/generated/android/NitroTextOnLoad.cpp new file mode 100644 index 0000000..a558792 --- /dev/null +++ b/nitrogen/generated/android/NitroTextOnLoad.cpp @@ -0,0 +1,50 @@ +/// +/// NitroTextOnLoad.cpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#ifndef BUILDING_NITROTEXT_WITH_GENERATED_CMAKE_PROJECT +#error NitroTextOnLoad.cpp is not being built with the autogenerated CMakeLists.txt project. Is a different CMakeLists.txt building this? +#endif + +#include "NitroTextOnLoad.hpp" + +#include +#include +#include + +#include "JHybridNitroTextSpec.hpp" +#include "JFunc_void_TextLayoutEvent.hpp" +#include "JFunc_void.hpp" +#include "views/JHybridNitroTextStateUpdater.hpp" +#include + +namespace margelo::nitro::nitrotext { + +int initialize(JavaVM* vm) { + using namespace margelo::nitro; + using namespace margelo::nitro::nitrotext; + using namespace facebook; + + return facebook::jni::initialize(vm, [] { + // Register native JNI methods + margelo::nitro::nitrotext::JHybridNitroTextSpec::registerNatives(); + margelo::nitro::nitrotext::JFunc_void_TextLayoutEvent_cxx::registerNatives(); + margelo::nitro::nitrotext::JFunc_void_cxx::registerNatives(); + margelo::nitro::nitrotext::views::JHybridNitroTextStateUpdater::registerNatives(); + + // Register Nitro Hybrid Objects + HybridObjectRegistry::registerHybridObjectConstructor( + "NitroText", + []() -> std::shared_ptr { + static DefaultConstructableObject object("com/nitrotext/HybridNitroText"); + auto instance = object.create(); + return instance->cthis()->shared(); + } + ); + }); +} + +} // namespace margelo::nitro::nitrotext diff --git a/nitrogen/generated/android/NitroTextOnLoad.hpp b/nitrogen/generated/android/NitroTextOnLoad.hpp new file mode 100644 index 0000000..762bf60 --- /dev/null +++ b/nitrogen/generated/android/NitroTextOnLoad.hpp @@ -0,0 +1,25 @@ +/// +/// NitroTextOnLoad.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#include +#include + +namespace margelo::nitro::nitrotext { + + /** + * Initializes the native (C++) part of NitroText, and autolinks all Hybrid Objects. + * Call this in your `JNI_OnLoad` function (probably inside `cpp-adapter.cpp`). + * Example: + * ```cpp (cpp-adapter.cpp) + * JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) { + * return margelo::nitro::nitrotext::initialize(vm); + * } + * ``` + */ + int initialize(JavaVM* vm); + +} // namespace margelo::nitro::nitrotext diff --git a/nitrogen/generated/android/c++/JDynamicTypeRamp.hpp b/nitrogen/generated/android/c++/JDynamicTypeRamp.hpp new file mode 100644 index 0000000..abbf037 --- /dev/null +++ b/nitrogen/generated/android/c++/JDynamicTypeRamp.hpp @@ -0,0 +1,86 @@ +/// +/// JDynamicTypeRamp.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#pragma once + +#include +#include "DynamicTypeRamp.hpp" + +namespace margelo::nitro::nitrotext { + + using namespace facebook; + + /** + * The C++ JNI bridge between the C++ enum "DynamicTypeRamp" and the the Kotlin enum "DynamicTypeRamp". + */ + struct JDynamicTypeRamp final: public jni::JavaClass { + public: + static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/nitrotext/DynamicTypeRamp;"; + + public: + /** + * Convert this Java/Kotlin-based enum to the C++ enum DynamicTypeRamp. + */ + [[maybe_unused]] + [[nodiscard]] + DynamicTypeRamp toCpp() const { + static const auto clazz = javaClassStatic(); + static const auto fieldOrdinal = clazz->getField("value"); + int ordinal = this->getFieldValue(fieldOrdinal); + return static_cast(ordinal); + } + + public: + /** + * Create a Java/Kotlin-based enum with the given C++ enum's value. + */ + [[maybe_unused]] + static jni::alias_ref fromCpp(DynamicTypeRamp value) { + static const auto clazz = javaClassStatic(); + static const auto fieldCAPTION2 = clazz->getStaticField("CAPTION2"); + static const auto fieldCAPTION1 = clazz->getStaticField("CAPTION1"); + static const auto fieldFOOTNOTE = clazz->getStaticField("FOOTNOTE"); + static const auto fieldSUBHEADLINE = clazz->getStaticField("SUBHEADLINE"); + static const auto fieldCALLOUT = clazz->getStaticField("CALLOUT"); + static const auto fieldBODY = clazz->getStaticField("BODY"); + static const auto fieldHEADLINE = clazz->getStaticField("HEADLINE"); + static const auto fieldTITLE3 = clazz->getStaticField("TITLE3"); + static const auto fieldTITLE2 = clazz->getStaticField("TITLE2"); + static const auto fieldTITLE1 = clazz->getStaticField("TITLE1"); + static const auto fieldLARGETITLE = clazz->getStaticField("LARGETITLE"); + + switch (value) { + case DynamicTypeRamp::CAPTION2: + return clazz->getStaticFieldValue(fieldCAPTION2); + case DynamicTypeRamp::CAPTION1: + return clazz->getStaticFieldValue(fieldCAPTION1); + case DynamicTypeRamp::FOOTNOTE: + return clazz->getStaticFieldValue(fieldFOOTNOTE); + case DynamicTypeRamp::SUBHEADLINE: + return clazz->getStaticFieldValue(fieldSUBHEADLINE); + case DynamicTypeRamp::CALLOUT: + return clazz->getStaticFieldValue(fieldCALLOUT); + case DynamicTypeRamp::BODY: + return clazz->getStaticFieldValue(fieldBODY); + case DynamicTypeRamp::HEADLINE: + return clazz->getStaticFieldValue(fieldHEADLINE); + case DynamicTypeRamp::TITLE3: + return clazz->getStaticFieldValue(fieldTITLE3); + case DynamicTypeRamp::TITLE2: + return clazz->getStaticFieldValue(fieldTITLE2); + case DynamicTypeRamp::TITLE1: + return clazz->getStaticFieldValue(fieldTITLE1); + case DynamicTypeRamp::LARGETITLE: + return clazz->getStaticFieldValue(fieldLARGETITLE); + default: + std::string stringValue = std::to_string(static_cast(value)); + throw std::invalid_argument("Invalid enum value (" + stringValue + "!"); + } + } + }; + +} // namespace margelo::nitro::nitrotext diff --git a/nitrogen/generated/android/c++/JEllipsizeMode.hpp b/nitrogen/generated/android/c++/JEllipsizeMode.hpp new file mode 100644 index 0000000..91ad21c --- /dev/null +++ b/nitrogen/generated/android/c++/JEllipsizeMode.hpp @@ -0,0 +1,65 @@ +/// +/// JEllipsizeMode.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#pragma once + +#include +#include "EllipsizeMode.hpp" + +namespace margelo::nitro::nitrotext { + + using namespace facebook; + + /** + * The C++ JNI bridge between the C++ enum "EllipsizeMode" and the the Kotlin enum "EllipsizeMode". + */ + struct JEllipsizeMode final: public jni::JavaClass { + public: + static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/nitrotext/EllipsizeMode;"; + + public: + /** + * Convert this Java/Kotlin-based enum to the C++ enum EllipsizeMode. + */ + [[maybe_unused]] + [[nodiscard]] + EllipsizeMode toCpp() const { + static const auto clazz = javaClassStatic(); + static const auto fieldOrdinal = clazz->getField("value"); + int ordinal = this->getFieldValue(fieldOrdinal); + return static_cast(ordinal); + } + + public: + /** + * Create a Java/Kotlin-based enum with the given C++ enum's value. + */ + [[maybe_unused]] + static jni::alias_ref fromCpp(EllipsizeMode value) { + static const auto clazz = javaClassStatic(); + static const auto fieldHEAD = clazz->getStaticField("HEAD"); + static const auto fieldMIDDLE = clazz->getStaticField("MIDDLE"); + static const auto fieldTAIL = clazz->getStaticField("TAIL"); + static const auto fieldCLIP = clazz->getStaticField("CLIP"); + + switch (value) { + case EllipsizeMode::HEAD: + return clazz->getStaticFieldValue(fieldHEAD); + case EllipsizeMode::MIDDLE: + return clazz->getStaticFieldValue(fieldMIDDLE); + case EllipsizeMode::TAIL: + return clazz->getStaticFieldValue(fieldTAIL); + case EllipsizeMode::CLIP: + return clazz->getStaticFieldValue(fieldCLIP); + default: + std::string stringValue = std::to_string(static_cast(value)); + throw std::invalid_argument("Invalid enum value (" + stringValue + "!"); + } + } + }; + +} // namespace margelo::nitro::nitrotext diff --git a/nitrogen/generated/android/c++/JFontStyle.hpp b/nitrogen/generated/android/c++/JFontStyle.hpp new file mode 100644 index 0000000..5d4a27f --- /dev/null +++ b/nitrogen/generated/android/c++/JFontStyle.hpp @@ -0,0 +1,62 @@ +/// +/// JFontStyle.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#pragma once + +#include +#include "FontStyle.hpp" + +namespace margelo::nitro::nitrotext { + + using namespace facebook; + + /** + * The C++ JNI bridge between the C++ enum "FontStyle" and the the Kotlin enum "FontStyle". + */ + struct JFontStyle final: public jni::JavaClass { + public: + static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/nitrotext/FontStyle;"; + + public: + /** + * Convert this Java/Kotlin-based enum to the C++ enum FontStyle. + */ + [[maybe_unused]] + [[nodiscard]] + FontStyle toCpp() const { + static const auto clazz = javaClassStatic(); + static const auto fieldOrdinal = clazz->getField("value"); + int ordinal = this->getFieldValue(fieldOrdinal); + return static_cast(ordinal); + } + + public: + /** + * Create a Java/Kotlin-based enum with the given C++ enum's value. + */ + [[maybe_unused]] + static jni::alias_ref fromCpp(FontStyle value) { + static const auto clazz = javaClassStatic(); + static const auto fieldNORMAL = clazz->getStaticField("NORMAL"); + static const auto fieldITALIC = clazz->getStaticField("ITALIC"); + static const auto fieldOBLIQUE = clazz->getStaticField("OBLIQUE"); + + switch (value) { + case FontStyle::NORMAL: + return clazz->getStaticFieldValue(fieldNORMAL); + case FontStyle::ITALIC: + return clazz->getStaticFieldValue(fieldITALIC); + case FontStyle::OBLIQUE: + return clazz->getStaticFieldValue(fieldOBLIQUE); + default: + std::string stringValue = std::to_string(static_cast(value)); + throw std::invalid_argument("Invalid enum value (" + stringValue + "!"); + } + } + }; + +} // namespace margelo::nitro::nitrotext diff --git a/nitrogen/generated/android/c++/JFontWeight.hpp b/nitrogen/generated/android/c++/JFontWeight.hpp new file mode 100644 index 0000000..a8c6440 --- /dev/null +++ b/nitrogen/generated/android/c++/JFontWeight.hpp @@ -0,0 +1,89 @@ +/// +/// JFontWeight.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#pragma once + +#include +#include "FontWeight.hpp" + +namespace margelo::nitro::nitrotext { + + using namespace facebook; + + /** + * The C++ JNI bridge between the C++ enum "FontWeight" and the the Kotlin enum "FontWeight". + */ + struct JFontWeight final: public jni::JavaClass { + public: + static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/nitrotext/FontWeight;"; + + public: + /** + * Convert this Java/Kotlin-based enum to the C++ enum FontWeight. + */ + [[maybe_unused]] + [[nodiscard]] + FontWeight toCpp() const { + static const auto clazz = javaClassStatic(); + static const auto fieldOrdinal = clazz->getField("value"); + int ordinal = this->getFieldValue(fieldOrdinal); + return static_cast(ordinal); + } + + public: + /** + * Create a Java/Kotlin-based enum with the given C++ enum's value. + */ + [[maybe_unused]] + static jni::alias_ref fromCpp(FontWeight value) { + static const auto clazz = javaClassStatic(); + static const auto fieldNORMAL = clazz->getStaticField("NORMAL"); + static const auto fieldBOLD = clazz->getStaticField("BOLD"); + static const auto fieldULTRALIGHT = clazz->getStaticField("ULTRALIGHT"); + static const auto fieldTHIN = clazz->getStaticField("THIN"); + static const auto fieldLIGHT = clazz->getStaticField("LIGHT"); + static const auto fieldMEDIUM = clazz->getStaticField("MEDIUM"); + static const auto fieldREGULAR = clazz->getStaticField("REGULAR"); + static const auto fieldSEMIBOLD = clazz->getStaticField("SEMIBOLD"); + static const auto fieldCONDENSEDBOLD = clazz->getStaticField("CONDENSEDBOLD"); + static const auto fieldCONDENSED = clazz->getStaticField("CONDENSED"); + static const auto fieldHEAVY = clazz->getStaticField("HEAVY"); + static const auto fieldBLACK = clazz->getStaticField("BLACK"); + + switch (value) { + case FontWeight::NORMAL: + return clazz->getStaticFieldValue(fieldNORMAL); + case FontWeight::BOLD: + return clazz->getStaticFieldValue(fieldBOLD); + case FontWeight::ULTRALIGHT: + return clazz->getStaticFieldValue(fieldULTRALIGHT); + case FontWeight::THIN: + return clazz->getStaticFieldValue(fieldTHIN); + case FontWeight::LIGHT: + return clazz->getStaticFieldValue(fieldLIGHT); + case FontWeight::MEDIUM: + return clazz->getStaticFieldValue(fieldMEDIUM); + case FontWeight::REGULAR: + return clazz->getStaticFieldValue(fieldREGULAR); + case FontWeight::SEMIBOLD: + return clazz->getStaticFieldValue(fieldSEMIBOLD); + case FontWeight::CONDENSEDBOLD: + return clazz->getStaticFieldValue(fieldCONDENSEDBOLD); + case FontWeight::CONDENSED: + return clazz->getStaticFieldValue(fieldCONDENSED); + case FontWeight::HEAVY: + return clazz->getStaticFieldValue(fieldHEAVY); + case FontWeight::BLACK: + return clazz->getStaticFieldValue(fieldBLACK); + default: + std::string stringValue = std::to_string(static_cast(value)); + throw std::invalid_argument("Invalid enum value (" + stringValue + "!"); + } + } + }; + +} // namespace margelo::nitro::nitrotext diff --git a/nitrogen/generated/android/c++/JFragment.hpp b/nitrogen/generated/android/c++/JFragment.hpp new file mode 100644 index 0000000..caaee25 --- /dev/null +++ b/nitrogen/generated/android/c++/JFragment.hpp @@ -0,0 +1,122 @@ +/// +/// JFragment.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#pragma once + +#include +#include "Fragment.hpp" + +#include "FontStyle.hpp" +#include "FontWeight.hpp" +#include "JFontStyle.hpp" +#include "JFontWeight.hpp" +#include "JTextAlign.hpp" +#include "JTextDecorationLine.hpp" +#include "JTextDecorationStyle.hpp" +#include "JTextTransform.hpp" +#include "TextAlign.hpp" +#include "TextDecorationLine.hpp" +#include "TextDecorationStyle.hpp" +#include "TextTransform.hpp" +#include +#include + +namespace margelo::nitro::nitrotext { + + using namespace facebook; + + /** + * The C++ JNI bridge between the C++ struct "Fragment" and the the Kotlin data class "Fragment". + */ + struct JFragment final: public jni::JavaClass { + public: + static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/nitrotext/Fragment;"; + + public: + /** + * Convert this Java/Kotlin-based struct to the C++ struct Fragment by copying all values to C++. + */ + [[maybe_unused]] + [[nodiscard]] + Fragment toCpp() const { + static const auto clazz = javaClassStatic(); + static const auto fieldText = clazz->getField("text"); + jni::local_ref text = this->getFieldValue(fieldText); + static const auto fieldSelectionColor = clazz->getField("selectionColor"); + jni::local_ref selectionColor = this->getFieldValue(fieldSelectionColor); + static const auto fieldFontSize = clazz->getField("fontSize"); + jni::local_ref fontSize = this->getFieldValue(fieldFontSize); + static const auto fieldFontWeight = clazz->getField("fontWeight"); + jni::local_ref fontWeight = this->getFieldValue(fieldFontWeight); + static const auto fieldFontColor = clazz->getField("fontColor"); + jni::local_ref fontColor = this->getFieldValue(fieldFontColor); + static const auto fieldFragmentBackgroundColor = clazz->getField("fragmentBackgroundColor"); + jni::local_ref fragmentBackgroundColor = this->getFieldValue(fieldFragmentBackgroundColor); + static const auto fieldFontStyle = clazz->getField("fontStyle"); + jni::local_ref fontStyle = this->getFieldValue(fieldFontStyle); + static const auto fieldFontFamily = clazz->getField("fontFamily"); + jni::local_ref fontFamily = this->getFieldValue(fieldFontFamily); + static const auto fieldLineHeight = clazz->getField("lineHeight"); + jni::local_ref lineHeight = this->getFieldValue(fieldLineHeight); + static const auto fieldLetterSpacing = clazz->getField("letterSpacing"); + jni::local_ref letterSpacing = this->getFieldValue(fieldLetterSpacing); + static const auto fieldTextAlign = clazz->getField("textAlign"); + jni::local_ref textAlign = this->getFieldValue(fieldTextAlign); + static const auto fieldTextTransform = clazz->getField("textTransform"); + jni::local_ref textTransform = this->getFieldValue(fieldTextTransform); + static const auto fieldTextDecorationLine = clazz->getField("textDecorationLine"); + jni::local_ref textDecorationLine = this->getFieldValue(fieldTextDecorationLine); + static const auto fieldTextDecorationColor = clazz->getField("textDecorationColor"); + jni::local_ref textDecorationColor = this->getFieldValue(fieldTextDecorationColor); + static const auto fieldTextDecorationStyle = clazz->getField("textDecorationStyle"); + jni::local_ref textDecorationStyle = this->getFieldValue(fieldTextDecorationStyle); + return Fragment( + text != nullptr ? std::make_optional(text->toStdString()) : std::nullopt, + selectionColor != nullptr ? std::make_optional(selectionColor->toStdString()) : std::nullopt, + fontSize != nullptr ? std::make_optional(fontSize->value()) : std::nullopt, + fontWeight != nullptr ? std::make_optional(fontWeight->toCpp()) : std::nullopt, + fontColor != nullptr ? std::make_optional(fontColor->toStdString()) : std::nullopt, + fragmentBackgroundColor != nullptr ? std::make_optional(fragmentBackgroundColor->toStdString()) : std::nullopt, + fontStyle != nullptr ? std::make_optional(fontStyle->toCpp()) : std::nullopt, + fontFamily != nullptr ? std::make_optional(fontFamily->toStdString()) : std::nullopt, + lineHeight != nullptr ? std::make_optional(lineHeight->value()) : std::nullopt, + letterSpacing != nullptr ? std::make_optional(letterSpacing->value()) : std::nullopt, + textAlign != nullptr ? std::make_optional(textAlign->toCpp()) : std::nullopt, + textTransform != nullptr ? std::make_optional(textTransform->toCpp()) : std::nullopt, + textDecorationLine != nullptr ? std::make_optional(textDecorationLine->toCpp()) : std::nullopt, + textDecorationColor != nullptr ? std::make_optional(textDecorationColor->toStdString()) : std::nullopt, + textDecorationStyle != nullptr ? std::make_optional(textDecorationStyle->toCpp()) : std::nullopt + ); + } + + public: + /** + * Create a Java/Kotlin-based struct by copying all values from the given C++ struct to Java. + */ + [[maybe_unused]] + static jni::local_ref fromCpp(const Fragment& value) { + return newInstance( + value.text.has_value() ? jni::make_jstring(value.text.value()) : nullptr, + value.selectionColor.has_value() ? jni::make_jstring(value.selectionColor.value()) : nullptr, + value.fontSize.has_value() ? jni::JDouble::valueOf(value.fontSize.value()) : nullptr, + value.fontWeight.has_value() ? JFontWeight::fromCpp(value.fontWeight.value()) : nullptr, + value.fontColor.has_value() ? jni::make_jstring(value.fontColor.value()) : nullptr, + value.fragmentBackgroundColor.has_value() ? jni::make_jstring(value.fragmentBackgroundColor.value()) : nullptr, + value.fontStyle.has_value() ? JFontStyle::fromCpp(value.fontStyle.value()) : nullptr, + value.fontFamily.has_value() ? jni::make_jstring(value.fontFamily.value()) : nullptr, + value.lineHeight.has_value() ? jni::JDouble::valueOf(value.lineHeight.value()) : nullptr, + value.letterSpacing.has_value() ? jni::JDouble::valueOf(value.letterSpacing.value()) : nullptr, + value.textAlign.has_value() ? JTextAlign::fromCpp(value.textAlign.value()) : nullptr, + value.textTransform.has_value() ? JTextTransform::fromCpp(value.textTransform.value()) : nullptr, + value.textDecorationLine.has_value() ? JTextDecorationLine::fromCpp(value.textDecorationLine.value()) : nullptr, + value.textDecorationColor.has_value() ? jni::make_jstring(value.textDecorationColor.value()) : nullptr, + value.textDecorationStyle.has_value() ? JTextDecorationStyle::fromCpp(value.textDecorationStyle.value()) : nullptr + ); + } + }; + +} // namespace margelo::nitro::nitrotext diff --git a/nitrogen/generated/android/c++/JFunc_void.hpp b/nitrogen/generated/android/c++/JFunc_void.hpp new file mode 100644 index 0000000..fab8e71 --- /dev/null +++ b/nitrogen/generated/android/c++/JFunc_void.hpp @@ -0,0 +1,74 @@ +/// +/// JFunc_void.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#pragma once + +#include +#include + +#include + +namespace margelo::nitro::nitrotext { + + using namespace facebook; + + /** + * Represents the Java/Kotlin callback `() -> Unit`. + * This can be passed around between C++ and Java/Kotlin. + */ + struct JFunc_void: public jni::JavaClass { + public: + static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/nitrotext/Func_void;"; + + public: + /** + * Invokes the function this `JFunc_void` instance holds through JNI. + */ + void invoke() const { + static const auto method = javaClassStatic()->getMethod("invoke"); + method(self()); + } + }; + + /** + * An implementation of Func_void that is backed by a C++ implementation (using `std::function<...>`) + */ + struct JFunc_void_cxx final: public jni::HybridClass { + public: + static jni::local_ref fromCpp(const std::function& func) { + return JFunc_void_cxx::newObjectCxxArgs(func); + } + + public: + /** + * Invokes the C++ `std::function<...>` this `JFunc_void_cxx` instance holds. + */ + void invoke_cxx() { + _func(); + } + + public: + [[nodiscard]] + inline const std::function& getFunction() const { + return _func; + } + + public: + static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/nitrotext/Func_void_cxx;"; + static void registerNatives() { + registerHybrid({makeNativeMethod("invoke_cxx", JFunc_void_cxx::invoke_cxx)}); + } + + private: + explicit JFunc_void_cxx(const std::function& func): _func(func) { } + + private: + friend HybridBase; + std::function _func; + }; + +} // namespace margelo::nitro::nitrotext diff --git a/nitrogen/generated/android/c++/JFunc_void_TextLayoutEvent.hpp b/nitrogen/generated/android/c++/JFunc_void_TextLayoutEvent.hpp new file mode 100644 index 0000000..c1ceac9 --- /dev/null +++ b/nitrogen/generated/android/c++/JFunc_void_TextLayoutEvent.hpp @@ -0,0 +1,80 @@ +/// +/// JFunc_void_TextLayoutEvent.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#pragma once + +#include +#include + +#include "TextLayoutEvent.hpp" +#include +#include "JTextLayoutEvent.hpp" +#include "TextLayout.hpp" +#include +#include "JTextLayout.hpp" +#include + +namespace margelo::nitro::nitrotext { + + using namespace facebook; + + /** + * Represents the Java/Kotlin callback `(layout: TextLayoutEvent) -> Unit`. + * This can be passed around between C++ and Java/Kotlin. + */ + struct JFunc_void_TextLayoutEvent: public jni::JavaClass { + public: + static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/nitrotext/Func_void_TextLayoutEvent;"; + + public: + /** + * Invokes the function this `JFunc_void_TextLayoutEvent` instance holds through JNI. + */ + void invoke(const TextLayoutEvent& layout) const { + static const auto method = javaClassStatic()->getMethod /* layout */)>("invoke"); + method(self(), JTextLayoutEvent::fromCpp(layout)); + } + }; + + /** + * An implementation of Func_void_TextLayoutEvent that is backed by a C++ implementation (using `std::function<...>`) + */ + struct JFunc_void_TextLayoutEvent_cxx final: public jni::HybridClass { + public: + static jni::local_ref fromCpp(const std::function& func) { + return JFunc_void_TextLayoutEvent_cxx::newObjectCxxArgs(func); + } + + public: + /** + * Invokes the C++ `std::function<...>` this `JFunc_void_TextLayoutEvent_cxx` instance holds. + */ + void invoke_cxx(jni::alias_ref layout) { + _func(layout->toCpp()); + } + + public: + [[nodiscard]] + inline const std::function& getFunction() const { + return _func; + } + + public: + static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/nitrotext/Func_void_TextLayoutEvent_cxx;"; + static void registerNatives() { + registerHybrid({makeNativeMethod("invoke_cxx", JFunc_void_TextLayoutEvent_cxx::invoke_cxx)}); + } + + private: + explicit JFunc_void_TextLayoutEvent_cxx(const std::function& func): _func(func) { } + + private: + friend HybridBase; + std::function _func; + }; + +} // namespace margelo::nitro::nitrotext diff --git a/nitrogen/generated/android/c++/JHybridNitroTextSpec.cpp b/nitrogen/generated/android/c++/JHybridNitroTextSpec.cpp new file mode 100644 index 0000000..9e036bb --- /dev/null +++ b/nitrogen/generated/android/c++/JHybridNitroTextSpec.cpp @@ -0,0 +1,411 @@ +/// +/// JHybridNitroTextSpec.cpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#include "JHybridNitroTextSpec.hpp" + +// Forward declaration of `Fragment` to properly resolve imports. +namespace margelo::nitro::nitrotext { struct Fragment; } +// Forward declaration of `FontWeight` to properly resolve imports. +namespace margelo::nitro::nitrotext { enum class FontWeight; } +// Forward declaration of `FontStyle` to properly resolve imports. +namespace margelo::nitro::nitrotext { enum class FontStyle; } +// Forward declaration of `TextAlign` to properly resolve imports. +namespace margelo::nitro::nitrotext { enum class TextAlign; } +// Forward declaration of `TextTransform` to properly resolve imports. +namespace margelo::nitro::nitrotext { enum class TextTransform; } +// Forward declaration of `TextDecorationLine` to properly resolve imports. +namespace margelo::nitro::nitrotext { enum class TextDecorationLine; } +// Forward declaration of `TextDecorationStyle` to properly resolve imports. +namespace margelo::nitro::nitrotext { enum class TextDecorationStyle; } +// Forward declaration of `EllipsizeMode` to properly resolve imports. +namespace margelo::nitro::nitrotext { enum class EllipsizeMode; } +// Forward declaration of `LineBreakStrategyIOS` to properly resolve imports. +namespace margelo::nitro::nitrotext { enum class LineBreakStrategyIOS; } +// Forward declaration of `DynamicTypeRamp` to properly resolve imports. +namespace margelo::nitro::nitrotext { enum class DynamicTypeRamp; } +// Forward declaration of `TextLayoutEvent` to properly resolve imports. +namespace margelo::nitro::nitrotext { struct TextLayoutEvent; } +// Forward declaration of `TextLayout` to properly resolve imports. +namespace margelo::nitro::nitrotext { struct TextLayout; } + +#include "Fragment.hpp" +#include +#include +#include "JFragment.hpp" +#include +#include "FontWeight.hpp" +#include "JFontWeight.hpp" +#include "FontStyle.hpp" +#include "JFontStyle.hpp" +#include "TextAlign.hpp" +#include "JTextAlign.hpp" +#include "TextTransform.hpp" +#include "JTextTransform.hpp" +#include "TextDecorationLine.hpp" +#include "JTextDecorationLine.hpp" +#include "TextDecorationStyle.hpp" +#include "JTextDecorationStyle.hpp" +#include "EllipsizeMode.hpp" +#include "JEllipsizeMode.hpp" +#include "LineBreakStrategyIOS.hpp" +#include "JLineBreakStrategyIOS.hpp" +#include "DynamicTypeRamp.hpp" +#include "JDynamicTypeRamp.hpp" +#include "TextLayoutEvent.hpp" +#include +#include "JFunc_void_TextLayoutEvent.hpp" +#include "JTextLayoutEvent.hpp" +#include "TextLayout.hpp" +#include "JTextLayout.hpp" +#include "JFunc_void.hpp" + +namespace margelo::nitro::nitrotext { + + jni::local_ref JHybridNitroTextSpec::initHybrid(jni::alias_ref jThis) { + return makeCxxInstance(jThis); + } + + void JHybridNitroTextSpec::registerNatives() { + registerHybrid({ + makeNativeMethod("initHybrid", JHybridNitroTextSpec::initHybrid), + }); + } + + size_t JHybridNitroTextSpec::getExternalMemorySize() noexcept { + static const auto method = javaClassStatic()->getMethod("getMemorySize"); + return method(_javaPart); + } + + void JHybridNitroTextSpec::dispose() noexcept { + static const auto method = javaClassStatic()->getMethod("dispose"); + method(_javaPart); + } + + // Properties + std::optional> JHybridNitroTextSpec::getFragments() { + static const auto method = javaClassStatic()->getMethod>()>("getFragments"); + auto __result = method(_javaPart); + return __result != nullptr ? std::make_optional([&]() { + size_t __size = __result->size(); + std::vector __vector; + __vector.reserve(__size); + for (size_t __i = 0; __i < __size; __i++) { + auto __element = __result->getElement(__i); + __vector.push_back(__element->toCpp()); + } + return __vector; + }()) : std::nullopt; + } + void JHybridNitroTextSpec::setFragments(const std::optional>& fragments) { + static const auto method = javaClassStatic()->getMethod> /* fragments */)>("setFragments"); + method(_javaPart, fragments.has_value() ? [&]() { + size_t __size = fragments.value().size(); + jni::local_ref> __array = jni::JArrayClass::newArray(__size); + for (size_t __i = 0; __i < __size; __i++) { + const auto& __element = fragments.value()[__i]; + __array->setElement(__i, *JFragment::fromCpp(__element)); + } + return __array; + }() : nullptr); + } + std::optional JHybridNitroTextSpec::getSelectable() { + static const auto method = javaClassStatic()->getMethod()>("getSelectable"); + auto __result = method(_javaPart); + return __result != nullptr ? std::make_optional(static_cast(__result->value())) : std::nullopt; + } + void JHybridNitroTextSpec::setSelectable(std::optional selectable) { + static const auto method = javaClassStatic()->getMethod /* selectable */)>("setSelectable"); + method(_javaPart, selectable.has_value() ? jni::JBoolean::valueOf(selectable.value()) : nullptr); + } + std::optional JHybridNitroTextSpec::getAllowFontScaling() { + static const auto method = javaClassStatic()->getMethod()>("getAllowFontScaling"); + auto __result = method(_javaPart); + return __result != nullptr ? std::make_optional(static_cast(__result->value())) : std::nullopt; + } + void JHybridNitroTextSpec::setAllowFontScaling(std::optional allowFontScaling) { + static const auto method = javaClassStatic()->getMethod /* allowFontScaling */)>("setAllowFontScaling"); + method(_javaPart, allowFontScaling.has_value() ? jni::JBoolean::valueOf(allowFontScaling.value()) : nullptr); + } + std::optional JHybridNitroTextSpec::getEllipsizeMode() { + static const auto method = javaClassStatic()->getMethod()>("getEllipsizeMode"); + auto __result = method(_javaPart); + return __result != nullptr ? std::make_optional(__result->toCpp()) : std::nullopt; + } + void JHybridNitroTextSpec::setEllipsizeMode(std::optional ellipsizeMode) { + static const auto method = javaClassStatic()->getMethod /* ellipsizeMode */)>("setEllipsizeMode"); + method(_javaPart, ellipsizeMode.has_value() ? JEllipsizeMode::fromCpp(ellipsizeMode.value()) : nullptr); + } + std::optional JHybridNitroTextSpec::getNumberOfLines() { + static const auto method = javaClassStatic()->getMethod()>("getNumberOfLines"); + auto __result = method(_javaPart); + return __result != nullptr ? std::make_optional(__result->value()) : std::nullopt; + } + void JHybridNitroTextSpec::setNumberOfLines(std::optional numberOfLines) { + static const auto method = javaClassStatic()->getMethod /* numberOfLines */)>("setNumberOfLines"); + method(_javaPart, numberOfLines.has_value() ? jni::JDouble::valueOf(numberOfLines.value()) : nullptr); + } + std::optional JHybridNitroTextSpec::getLineBreakStrategyIOS() { + static const auto method = javaClassStatic()->getMethod()>("getLineBreakStrategyIOS"); + auto __result = method(_javaPart); + return __result != nullptr ? std::make_optional(__result->toCpp()) : std::nullopt; + } + void JHybridNitroTextSpec::setLineBreakStrategyIOS(std::optional lineBreakStrategyIOS) { + static const auto method = javaClassStatic()->getMethod /* lineBreakStrategyIOS */)>("setLineBreakStrategyIOS"); + method(_javaPart, lineBreakStrategyIOS.has_value() ? JLineBreakStrategyIOS::fromCpp(lineBreakStrategyIOS.value()) : nullptr); + } + std::optional JHybridNitroTextSpec::getDynamicTypeRamp() { + static const auto method = javaClassStatic()->getMethod()>("getDynamicTypeRamp"); + auto __result = method(_javaPart); + return __result != nullptr ? std::make_optional(__result->toCpp()) : std::nullopt; + } + void JHybridNitroTextSpec::setDynamicTypeRamp(std::optional dynamicTypeRamp) { + static const auto method = javaClassStatic()->getMethod /* dynamicTypeRamp */)>("setDynamicTypeRamp"); + method(_javaPart, dynamicTypeRamp.has_value() ? JDynamicTypeRamp::fromCpp(dynamicTypeRamp.value()) : nullptr); + } + std::optional JHybridNitroTextSpec::getMaxFontSizeMultiplier() { + static const auto method = javaClassStatic()->getMethod()>("getMaxFontSizeMultiplier"); + auto __result = method(_javaPart); + return __result != nullptr ? std::make_optional(__result->value()) : std::nullopt; + } + void JHybridNitroTextSpec::setMaxFontSizeMultiplier(std::optional maxFontSizeMultiplier) { + static const auto method = javaClassStatic()->getMethod /* maxFontSizeMultiplier */)>("setMaxFontSizeMultiplier"); + method(_javaPart, maxFontSizeMultiplier.has_value() ? jni::JDouble::valueOf(maxFontSizeMultiplier.value()) : nullptr); + } + std::optional JHybridNitroTextSpec::getAdjustsFontSizeToFit() { + static const auto method = javaClassStatic()->getMethod()>("getAdjustsFontSizeToFit"); + auto __result = method(_javaPart); + return __result != nullptr ? std::make_optional(static_cast(__result->value())) : std::nullopt; + } + void JHybridNitroTextSpec::setAdjustsFontSizeToFit(std::optional adjustsFontSizeToFit) { + static const auto method = javaClassStatic()->getMethod /* adjustsFontSizeToFit */)>("setAdjustsFontSizeToFit"); + method(_javaPart, adjustsFontSizeToFit.has_value() ? jni::JBoolean::valueOf(adjustsFontSizeToFit.value()) : nullptr); + } + std::optional JHybridNitroTextSpec::getMinimumFontScale() { + static const auto method = javaClassStatic()->getMethod()>("getMinimumFontScale"); + auto __result = method(_javaPart); + return __result != nullptr ? std::make_optional(__result->value()) : std::nullopt; + } + void JHybridNitroTextSpec::setMinimumFontScale(std::optional minimumFontScale) { + static const auto method = javaClassStatic()->getMethod /* minimumFontScale */)>("setMinimumFontScale"); + method(_javaPart, minimumFontScale.has_value() ? jni::JDouble::valueOf(minimumFontScale.value()) : nullptr); + } + std::optional> JHybridNitroTextSpec::getOnTextLayout() { + static const auto method = javaClassStatic()->getMethod()>("getOnTextLayout_cxx"); + auto __result = method(_javaPart); + return __result != nullptr ? std::make_optional([&]() -> std::function { + if (__result->isInstanceOf(JFunc_void_TextLayoutEvent_cxx::javaClassStatic())) [[likely]] { + auto downcast = jni::static_ref_cast(__result); + return downcast->cthis()->getFunction(); + } else { + auto __resultRef = jni::make_global(__result); + return [__resultRef](TextLayoutEvent layout) -> void { + return __resultRef->invoke(layout); + }; + } + }()) : std::nullopt; + } + void JHybridNitroTextSpec::setOnTextLayout(const std::optional>& onTextLayout) { + static const auto method = javaClassStatic()->getMethod /* onTextLayout */)>("setOnTextLayout_cxx"); + method(_javaPart, onTextLayout.has_value() ? JFunc_void_TextLayoutEvent_cxx::fromCpp(onTextLayout.value()) : nullptr); + } + std::optional> JHybridNitroTextSpec::getOnPress() { + static const auto method = javaClassStatic()->getMethod()>("getOnPress_cxx"); + auto __result = method(_javaPart); + return __result != nullptr ? std::make_optional([&]() -> std::function { + if (__result->isInstanceOf(JFunc_void_cxx::javaClassStatic())) [[likely]] { + auto downcast = jni::static_ref_cast(__result); + return downcast->cthis()->getFunction(); + } else { + auto __resultRef = jni::make_global(__result); + return [__resultRef]() -> void { + return __resultRef->invoke(); + }; + } + }()) : std::nullopt; + } + void JHybridNitroTextSpec::setOnPress(const std::optional>& onPress) { + static const auto method = javaClassStatic()->getMethod /* onPress */)>("setOnPress_cxx"); + method(_javaPart, onPress.has_value() ? JFunc_void_cxx::fromCpp(onPress.value()) : nullptr); + } + std::optional> JHybridNitroTextSpec::getOnPressIn() { + static const auto method = javaClassStatic()->getMethod()>("getOnPressIn_cxx"); + auto __result = method(_javaPart); + return __result != nullptr ? std::make_optional([&]() -> std::function { + if (__result->isInstanceOf(JFunc_void_cxx::javaClassStatic())) [[likely]] { + auto downcast = jni::static_ref_cast(__result); + return downcast->cthis()->getFunction(); + } else { + auto __resultRef = jni::make_global(__result); + return [__resultRef]() -> void { + return __resultRef->invoke(); + }; + } + }()) : std::nullopt; + } + void JHybridNitroTextSpec::setOnPressIn(const std::optional>& onPressIn) { + static const auto method = javaClassStatic()->getMethod /* onPressIn */)>("setOnPressIn_cxx"); + method(_javaPart, onPressIn.has_value() ? JFunc_void_cxx::fromCpp(onPressIn.value()) : nullptr); + } + std::optional> JHybridNitroTextSpec::getOnPressOut() { + static const auto method = javaClassStatic()->getMethod()>("getOnPressOut_cxx"); + auto __result = method(_javaPart); + return __result != nullptr ? std::make_optional([&]() -> std::function { + if (__result->isInstanceOf(JFunc_void_cxx::javaClassStatic())) [[likely]] { + auto downcast = jni::static_ref_cast(__result); + return downcast->cthis()->getFunction(); + } else { + auto __resultRef = jni::make_global(__result); + return [__resultRef]() -> void { + return __resultRef->invoke(); + }; + } + }()) : std::nullopt; + } + void JHybridNitroTextSpec::setOnPressOut(const std::optional>& onPressOut) { + static const auto method = javaClassStatic()->getMethod /* onPressOut */)>("setOnPressOut_cxx"); + method(_javaPart, onPressOut.has_value() ? JFunc_void_cxx::fromCpp(onPressOut.value()) : nullptr); + } + std::optional JHybridNitroTextSpec::getText() { + static const auto method = javaClassStatic()->getMethod()>("getText"); + auto __result = method(_javaPart); + return __result != nullptr ? std::make_optional(__result->toStdString()) : std::nullopt; + } + void JHybridNitroTextSpec::setText(const std::optional& text) { + static const auto method = javaClassStatic()->getMethod /* text */)>("setText"); + method(_javaPart, text.has_value() ? jni::make_jstring(text.value()) : nullptr); + } + std::optional JHybridNitroTextSpec::getSelectionColor() { + static const auto method = javaClassStatic()->getMethod()>("getSelectionColor"); + auto __result = method(_javaPart); + return __result != nullptr ? std::make_optional(__result->toStdString()) : std::nullopt; + } + void JHybridNitroTextSpec::setSelectionColor(const std::optional& selectionColor) { + static const auto method = javaClassStatic()->getMethod /* selectionColor */)>("setSelectionColor"); + method(_javaPart, selectionColor.has_value() ? jni::make_jstring(selectionColor.value()) : nullptr); + } + std::optional JHybridNitroTextSpec::getFontSize() { + static const auto method = javaClassStatic()->getMethod()>("getFontSize"); + auto __result = method(_javaPart); + return __result != nullptr ? std::make_optional(__result->value()) : std::nullopt; + } + void JHybridNitroTextSpec::setFontSize(std::optional fontSize) { + static const auto method = javaClassStatic()->getMethod /* fontSize */)>("setFontSize"); + method(_javaPart, fontSize.has_value() ? jni::JDouble::valueOf(fontSize.value()) : nullptr); + } + std::optional JHybridNitroTextSpec::getFontWeight() { + static const auto method = javaClassStatic()->getMethod()>("getFontWeight"); + auto __result = method(_javaPart); + return __result != nullptr ? std::make_optional(__result->toCpp()) : std::nullopt; + } + void JHybridNitroTextSpec::setFontWeight(std::optional fontWeight) { + static const auto method = javaClassStatic()->getMethod /* fontWeight */)>("setFontWeight"); + method(_javaPart, fontWeight.has_value() ? JFontWeight::fromCpp(fontWeight.value()) : nullptr); + } + std::optional JHybridNitroTextSpec::getFontColor() { + static const auto method = javaClassStatic()->getMethod()>("getFontColor"); + auto __result = method(_javaPart); + return __result != nullptr ? std::make_optional(__result->toStdString()) : std::nullopt; + } + void JHybridNitroTextSpec::setFontColor(const std::optional& fontColor) { + static const auto method = javaClassStatic()->getMethod /* fontColor */)>("setFontColor"); + method(_javaPart, fontColor.has_value() ? jni::make_jstring(fontColor.value()) : nullptr); + } + std::optional JHybridNitroTextSpec::getFragmentBackgroundColor() { + static const auto method = javaClassStatic()->getMethod()>("getFragmentBackgroundColor"); + auto __result = method(_javaPart); + return __result != nullptr ? std::make_optional(__result->toStdString()) : std::nullopt; + } + void JHybridNitroTextSpec::setFragmentBackgroundColor(const std::optional& fragmentBackgroundColor) { + static const auto method = javaClassStatic()->getMethod /* fragmentBackgroundColor */)>("setFragmentBackgroundColor"); + method(_javaPart, fragmentBackgroundColor.has_value() ? jni::make_jstring(fragmentBackgroundColor.value()) : nullptr); + } + std::optional JHybridNitroTextSpec::getFontStyle() { + static const auto method = javaClassStatic()->getMethod()>("getFontStyle"); + auto __result = method(_javaPart); + return __result != nullptr ? std::make_optional(__result->toCpp()) : std::nullopt; + } + void JHybridNitroTextSpec::setFontStyle(std::optional fontStyle) { + static const auto method = javaClassStatic()->getMethod /* fontStyle */)>("setFontStyle"); + method(_javaPart, fontStyle.has_value() ? JFontStyle::fromCpp(fontStyle.value()) : nullptr); + } + std::optional JHybridNitroTextSpec::getFontFamily() { + static const auto method = javaClassStatic()->getMethod()>("getFontFamily"); + auto __result = method(_javaPart); + return __result != nullptr ? std::make_optional(__result->toStdString()) : std::nullopt; + } + void JHybridNitroTextSpec::setFontFamily(const std::optional& fontFamily) { + static const auto method = javaClassStatic()->getMethod /* fontFamily */)>("setFontFamily"); + method(_javaPart, fontFamily.has_value() ? jni::make_jstring(fontFamily.value()) : nullptr); + } + std::optional JHybridNitroTextSpec::getLineHeight() { + static const auto method = javaClassStatic()->getMethod()>("getLineHeight"); + auto __result = method(_javaPart); + return __result != nullptr ? std::make_optional(__result->value()) : std::nullopt; + } + void JHybridNitroTextSpec::setLineHeight(std::optional lineHeight) { + static const auto method = javaClassStatic()->getMethod /* lineHeight */)>("setLineHeight"); + method(_javaPart, lineHeight.has_value() ? jni::JDouble::valueOf(lineHeight.value()) : nullptr); + } + std::optional JHybridNitroTextSpec::getLetterSpacing() { + static const auto method = javaClassStatic()->getMethod()>("getLetterSpacing"); + auto __result = method(_javaPart); + return __result != nullptr ? std::make_optional(__result->value()) : std::nullopt; + } + void JHybridNitroTextSpec::setLetterSpacing(std::optional letterSpacing) { + static const auto method = javaClassStatic()->getMethod /* letterSpacing */)>("setLetterSpacing"); + method(_javaPart, letterSpacing.has_value() ? jni::JDouble::valueOf(letterSpacing.value()) : nullptr); + } + std::optional JHybridNitroTextSpec::getTextAlign() { + static const auto method = javaClassStatic()->getMethod()>("getTextAlign"); + auto __result = method(_javaPart); + return __result != nullptr ? std::make_optional(__result->toCpp()) : std::nullopt; + } + void JHybridNitroTextSpec::setTextAlign(std::optional textAlign) { + static const auto method = javaClassStatic()->getMethod /* textAlign */)>("setTextAlign"); + method(_javaPart, textAlign.has_value() ? JTextAlign::fromCpp(textAlign.value()) : nullptr); + } + std::optional JHybridNitroTextSpec::getTextTransform() { + static const auto method = javaClassStatic()->getMethod()>("getTextTransform"); + auto __result = method(_javaPart); + return __result != nullptr ? std::make_optional(__result->toCpp()) : std::nullopt; + } + void JHybridNitroTextSpec::setTextTransform(std::optional textTransform) { + static const auto method = javaClassStatic()->getMethod /* textTransform */)>("setTextTransform"); + method(_javaPart, textTransform.has_value() ? JTextTransform::fromCpp(textTransform.value()) : nullptr); + } + std::optional JHybridNitroTextSpec::getTextDecorationLine() { + static const auto method = javaClassStatic()->getMethod()>("getTextDecorationLine"); + auto __result = method(_javaPart); + return __result != nullptr ? std::make_optional(__result->toCpp()) : std::nullopt; + } + void JHybridNitroTextSpec::setTextDecorationLine(std::optional textDecorationLine) { + static const auto method = javaClassStatic()->getMethod /* textDecorationLine */)>("setTextDecorationLine"); + method(_javaPart, textDecorationLine.has_value() ? JTextDecorationLine::fromCpp(textDecorationLine.value()) : nullptr); + } + std::optional JHybridNitroTextSpec::getTextDecorationColor() { + static const auto method = javaClassStatic()->getMethod()>("getTextDecorationColor"); + auto __result = method(_javaPart); + return __result != nullptr ? std::make_optional(__result->toStdString()) : std::nullopt; + } + void JHybridNitroTextSpec::setTextDecorationColor(const std::optional& textDecorationColor) { + static const auto method = javaClassStatic()->getMethod /* textDecorationColor */)>("setTextDecorationColor"); + method(_javaPart, textDecorationColor.has_value() ? jni::make_jstring(textDecorationColor.value()) : nullptr); + } + std::optional JHybridNitroTextSpec::getTextDecorationStyle() { + static const auto method = javaClassStatic()->getMethod()>("getTextDecorationStyle"); + auto __result = method(_javaPart); + return __result != nullptr ? std::make_optional(__result->toCpp()) : std::nullopt; + } + void JHybridNitroTextSpec::setTextDecorationStyle(std::optional textDecorationStyle) { + static const auto method = javaClassStatic()->getMethod /* textDecorationStyle */)>("setTextDecorationStyle"); + method(_javaPart, textDecorationStyle.has_value() ? JTextDecorationStyle::fromCpp(textDecorationStyle.value()) : nullptr); + } + + // Methods + + +} // namespace margelo::nitro::nitrotext diff --git a/nitrogen/generated/android/c++/JHybridNitroTextSpec.hpp b/nitrogen/generated/android/c++/JHybridNitroTextSpec.hpp new file mode 100644 index 0000000..6c2a518 --- /dev/null +++ b/nitrogen/generated/android/c++/JHybridNitroTextSpec.hpp @@ -0,0 +1,121 @@ +/// +/// HybridNitroTextSpec.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#pragma once + +#include +#include +#include "HybridNitroTextSpec.hpp" + + + + +namespace margelo::nitro::nitrotext { + + using namespace facebook; + + class JHybridNitroTextSpec: public jni::HybridClass, + public virtual HybridNitroTextSpec { + public: + static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/nitrotext/HybridNitroTextSpec;"; + static jni::local_ref initHybrid(jni::alias_ref jThis); + static void registerNatives(); + + protected: + // C++ constructor (called from Java via `initHybrid()`) + explicit JHybridNitroTextSpec(jni::alias_ref jThis) : + HybridObject(HybridNitroTextSpec::TAG), + HybridBase(jThis), + _javaPart(jni::make_global(jThis)) {} + + public: + ~JHybridNitroTextSpec() override { + // Hermes GC can destroy JS objects on a non-JNI Thread. + jni::ThreadScope::WithClassLoader([&] { _javaPart.reset(); }); + } + + public: + size_t getExternalMemorySize() noexcept override; + void dispose() noexcept override; + + public: + inline const jni::global_ref& getJavaPart() const noexcept { + return _javaPart; + } + + public: + // Properties + std::optional> getFragments() override; + void setFragments(const std::optional>& fragments) override; + std::optional getSelectable() override; + void setSelectable(std::optional selectable) override; + std::optional getAllowFontScaling() override; + void setAllowFontScaling(std::optional allowFontScaling) override; + std::optional getEllipsizeMode() override; + void setEllipsizeMode(std::optional ellipsizeMode) override; + std::optional getNumberOfLines() override; + void setNumberOfLines(std::optional numberOfLines) override; + std::optional getLineBreakStrategyIOS() override; + void setLineBreakStrategyIOS(std::optional lineBreakStrategyIOS) override; + std::optional getDynamicTypeRamp() override; + void setDynamicTypeRamp(std::optional dynamicTypeRamp) override; + std::optional getMaxFontSizeMultiplier() override; + void setMaxFontSizeMultiplier(std::optional maxFontSizeMultiplier) override; + std::optional getAdjustsFontSizeToFit() override; + void setAdjustsFontSizeToFit(std::optional adjustsFontSizeToFit) override; + std::optional getMinimumFontScale() override; + void setMinimumFontScale(std::optional minimumFontScale) override; + std::optional> getOnTextLayout() override; + void setOnTextLayout(const std::optional>& onTextLayout) override; + std::optional> getOnPress() override; + void setOnPress(const std::optional>& onPress) override; + std::optional> getOnPressIn() override; + void setOnPressIn(const std::optional>& onPressIn) override; + std::optional> getOnPressOut() override; + void setOnPressOut(const std::optional>& onPressOut) override; + std::optional getText() override; + void setText(const std::optional& text) override; + std::optional getSelectionColor() override; + void setSelectionColor(const std::optional& selectionColor) override; + std::optional getFontSize() override; + void setFontSize(std::optional fontSize) override; + std::optional getFontWeight() override; + void setFontWeight(std::optional fontWeight) override; + std::optional getFontColor() override; + void setFontColor(const std::optional& fontColor) override; + std::optional getFragmentBackgroundColor() override; + void setFragmentBackgroundColor(const std::optional& fragmentBackgroundColor) override; + std::optional getFontStyle() override; + void setFontStyle(std::optional fontStyle) override; + std::optional getFontFamily() override; + void setFontFamily(const std::optional& fontFamily) override; + std::optional getLineHeight() override; + void setLineHeight(std::optional lineHeight) override; + std::optional getLetterSpacing() override; + void setLetterSpacing(std::optional letterSpacing) override; + std::optional getTextAlign() override; + void setTextAlign(std::optional textAlign) override; + std::optional getTextTransform() override; + void setTextTransform(std::optional textTransform) override; + std::optional getTextDecorationLine() override; + void setTextDecorationLine(std::optional textDecorationLine) override; + std::optional getTextDecorationColor() override; + void setTextDecorationColor(const std::optional& textDecorationColor) override; + std::optional getTextDecorationStyle() override; + void setTextDecorationStyle(std::optional textDecorationStyle) override; + + public: + // Methods + + + private: + friend HybridBase; + using HybridBase::HybridBase; + jni::global_ref _javaPart; + }; + +} // namespace margelo::nitro::nitrotext diff --git a/nitrogen/generated/android/c++/JLineBreakStrategyIOS.hpp b/nitrogen/generated/android/c++/JLineBreakStrategyIOS.hpp new file mode 100644 index 0000000..feafca9 --- /dev/null +++ b/nitrogen/generated/android/c++/JLineBreakStrategyIOS.hpp @@ -0,0 +1,65 @@ +/// +/// JLineBreakStrategyIOS.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#pragma once + +#include +#include "LineBreakStrategyIOS.hpp" + +namespace margelo::nitro::nitrotext { + + using namespace facebook; + + /** + * The C++ JNI bridge between the C++ enum "LineBreakStrategyIOS" and the the Kotlin enum "LineBreakStrategyIOS". + */ + struct JLineBreakStrategyIOS final: public jni::JavaClass { + public: + static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/nitrotext/LineBreakStrategyIOS;"; + + public: + /** + * Convert this Java/Kotlin-based enum to the C++ enum LineBreakStrategyIOS. + */ + [[maybe_unused]] + [[nodiscard]] + LineBreakStrategyIOS toCpp() const { + static const auto clazz = javaClassStatic(); + static const auto fieldOrdinal = clazz->getField("value"); + int ordinal = this->getFieldValue(fieldOrdinal); + return static_cast(ordinal); + } + + public: + /** + * Create a Java/Kotlin-based enum with the given C++ enum's value. + */ + [[maybe_unused]] + static jni::alias_ref fromCpp(LineBreakStrategyIOS value) { + static const auto clazz = javaClassStatic(); + static const auto fieldNONE = clazz->getStaticField("NONE"); + static const auto fieldSTANDARD = clazz->getStaticField("STANDARD"); + static const auto fieldHANGUL_WORD = clazz->getStaticField("HANGUL_WORD"); + static const auto fieldPUSH_OUT = clazz->getStaticField("PUSH_OUT"); + + switch (value) { + case LineBreakStrategyIOS::NONE: + return clazz->getStaticFieldValue(fieldNONE); + case LineBreakStrategyIOS::STANDARD: + return clazz->getStaticFieldValue(fieldSTANDARD); + case LineBreakStrategyIOS::HANGUL_WORD: + return clazz->getStaticFieldValue(fieldHANGUL_WORD); + case LineBreakStrategyIOS::PUSH_OUT: + return clazz->getStaticFieldValue(fieldPUSH_OUT); + default: + std::string stringValue = std::to_string(static_cast(value)); + throw std::invalid_argument("Invalid enum value (" + stringValue + "!"); + } + } + }; + +} // namespace margelo::nitro::nitrotext diff --git a/nitrogen/generated/android/c++/JTextAlign.hpp b/nitrogen/generated/android/c++/JTextAlign.hpp new file mode 100644 index 0000000..1bf081b --- /dev/null +++ b/nitrogen/generated/android/c++/JTextAlign.hpp @@ -0,0 +1,68 @@ +/// +/// JTextAlign.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#pragma once + +#include +#include "TextAlign.hpp" + +namespace margelo::nitro::nitrotext { + + using namespace facebook; + + /** + * The C++ JNI bridge between the C++ enum "TextAlign" and the the Kotlin enum "TextAlign". + */ + struct JTextAlign final: public jni::JavaClass { + public: + static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/nitrotext/TextAlign;"; + + public: + /** + * Convert this Java/Kotlin-based enum to the C++ enum TextAlign. + */ + [[maybe_unused]] + [[nodiscard]] + TextAlign toCpp() const { + static const auto clazz = javaClassStatic(); + static const auto fieldOrdinal = clazz->getField("value"); + int ordinal = this->getFieldValue(fieldOrdinal); + return static_cast(ordinal); + } + + public: + /** + * Create a Java/Kotlin-based enum with the given C++ enum's value. + */ + [[maybe_unused]] + static jni::alias_ref fromCpp(TextAlign value) { + static const auto clazz = javaClassStatic(); + static const auto fieldAUTO = clazz->getStaticField("AUTO"); + static const auto fieldLEFT = clazz->getStaticField("LEFT"); + static const auto fieldRIGHT = clazz->getStaticField("RIGHT"); + static const auto fieldCENTER = clazz->getStaticField("CENTER"); + static const auto fieldJUSTIFY = clazz->getStaticField("JUSTIFY"); + + switch (value) { + case TextAlign::AUTO: + return clazz->getStaticFieldValue(fieldAUTO); + case TextAlign::LEFT: + return clazz->getStaticFieldValue(fieldLEFT); + case TextAlign::RIGHT: + return clazz->getStaticFieldValue(fieldRIGHT); + case TextAlign::CENTER: + return clazz->getStaticFieldValue(fieldCENTER); + case TextAlign::JUSTIFY: + return clazz->getStaticFieldValue(fieldJUSTIFY); + default: + std::string stringValue = std::to_string(static_cast(value)); + throw std::invalid_argument("Invalid enum value (" + stringValue + "!"); + } + } + }; + +} // namespace margelo::nitro::nitrotext diff --git a/nitrogen/generated/android/c++/JTextDecorationLine.hpp b/nitrogen/generated/android/c++/JTextDecorationLine.hpp new file mode 100644 index 0000000..143cc7e --- /dev/null +++ b/nitrogen/generated/android/c++/JTextDecorationLine.hpp @@ -0,0 +1,65 @@ +/// +/// JTextDecorationLine.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#pragma once + +#include +#include "TextDecorationLine.hpp" + +namespace margelo::nitro::nitrotext { + + using namespace facebook; + + /** + * The C++ JNI bridge between the C++ enum "TextDecorationLine" and the the Kotlin enum "TextDecorationLine". + */ + struct JTextDecorationLine final: public jni::JavaClass { + public: + static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/nitrotext/TextDecorationLine;"; + + public: + /** + * Convert this Java/Kotlin-based enum to the C++ enum TextDecorationLine. + */ + [[maybe_unused]] + [[nodiscard]] + TextDecorationLine toCpp() const { + static const auto clazz = javaClassStatic(); + static const auto fieldOrdinal = clazz->getField("value"); + int ordinal = this->getFieldValue(fieldOrdinal); + return static_cast(ordinal); + } + + public: + /** + * Create a Java/Kotlin-based enum with the given C++ enum's value. + */ + [[maybe_unused]] + static jni::alias_ref fromCpp(TextDecorationLine value) { + static const auto clazz = javaClassStatic(); + static const auto fieldNONE = clazz->getStaticField("NONE"); + static const auto fieldUNDERLINE = clazz->getStaticField("UNDERLINE"); + static const auto fieldLINE_THROUGH = clazz->getStaticField("LINE_THROUGH"); + static const auto fieldUNDERLINE_LINE_THROUGH = clazz->getStaticField("UNDERLINE_LINE_THROUGH"); + + switch (value) { + case TextDecorationLine::NONE: + return clazz->getStaticFieldValue(fieldNONE); + case TextDecorationLine::UNDERLINE: + return clazz->getStaticFieldValue(fieldUNDERLINE); + case TextDecorationLine::LINE_THROUGH: + return clazz->getStaticFieldValue(fieldLINE_THROUGH); + case TextDecorationLine::UNDERLINE_LINE_THROUGH: + return clazz->getStaticFieldValue(fieldUNDERLINE_LINE_THROUGH); + default: + std::string stringValue = std::to_string(static_cast(value)); + throw std::invalid_argument("Invalid enum value (" + stringValue + "!"); + } + } + }; + +} // namespace margelo::nitro::nitrotext diff --git a/nitrogen/generated/android/c++/JTextDecorationStyle.hpp b/nitrogen/generated/android/c++/JTextDecorationStyle.hpp new file mode 100644 index 0000000..1a459c1 --- /dev/null +++ b/nitrogen/generated/android/c++/JTextDecorationStyle.hpp @@ -0,0 +1,65 @@ +/// +/// JTextDecorationStyle.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#pragma once + +#include +#include "TextDecorationStyle.hpp" + +namespace margelo::nitro::nitrotext { + + using namespace facebook; + + /** + * The C++ JNI bridge between the C++ enum "TextDecorationStyle" and the the Kotlin enum "TextDecorationStyle". + */ + struct JTextDecorationStyle final: public jni::JavaClass { + public: + static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/nitrotext/TextDecorationStyle;"; + + public: + /** + * Convert this Java/Kotlin-based enum to the C++ enum TextDecorationStyle. + */ + [[maybe_unused]] + [[nodiscard]] + TextDecorationStyle toCpp() const { + static const auto clazz = javaClassStatic(); + static const auto fieldOrdinal = clazz->getField("value"); + int ordinal = this->getFieldValue(fieldOrdinal); + return static_cast(ordinal); + } + + public: + /** + * Create a Java/Kotlin-based enum with the given C++ enum's value. + */ + [[maybe_unused]] + static jni::alias_ref fromCpp(TextDecorationStyle value) { + static const auto clazz = javaClassStatic(); + static const auto fieldSOLID = clazz->getStaticField("SOLID"); + static const auto fieldDOUBLE = clazz->getStaticField("DOUBLE"); + static const auto fieldDOTTED = clazz->getStaticField("DOTTED"); + static const auto fieldDASHED = clazz->getStaticField("DASHED"); + + switch (value) { + case TextDecorationStyle::SOLID: + return clazz->getStaticFieldValue(fieldSOLID); + case TextDecorationStyle::DOUBLE: + return clazz->getStaticFieldValue(fieldDOUBLE); + case TextDecorationStyle::DOTTED: + return clazz->getStaticFieldValue(fieldDOTTED); + case TextDecorationStyle::DASHED: + return clazz->getStaticFieldValue(fieldDASHED); + default: + std::string stringValue = std::to_string(static_cast(value)); + throw std::invalid_argument("Invalid enum value (" + stringValue + "!"); + } + } + }; + +} // namespace margelo::nitro::nitrotext diff --git a/nitrogen/generated/android/c++/JTextLayout.hpp b/nitrogen/generated/android/c++/JTextLayout.hpp new file mode 100644 index 0000000..0224ce2 --- /dev/null +++ b/nitrogen/generated/android/c++/JTextLayout.hpp @@ -0,0 +1,85 @@ +/// +/// JTextLayout.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#pragma once + +#include +#include "TextLayout.hpp" + +#include + +namespace margelo::nitro::nitrotext { + + using namespace facebook; + + /** + * The C++ JNI bridge between the C++ struct "TextLayout" and the the Kotlin data class "TextLayout". + */ + struct JTextLayout final: public jni::JavaClass { + public: + static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/nitrotext/TextLayout;"; + + public: + /** + * Convert this Java/Kotlin-based struct to the C++ struct TextLayout by copying all values to C++. + */ + [[maybe_unused]] + [[nodiscard]] + TextLayout toCpp() const { + static const auto clazz = javaClassStatic(); + static const auto fieldText = clazz->getField("text"); + jni::local_ref text = this->getFieldValue(fieldText); + static const auto fieldX = clazz->getField("x"); + double x = this->getFieldValue(fieldX); + static const auto fieldY = clazz->getField("y"); + double y = this->getFieldValue(fieldY); + static const auto fieldWidth = clazz->getField("width"); + double width = this->getFieldValue(fieldWidth); + static const auto fieldHeight = clazz->getField("height"); + double height = this->getFieldValue(fieldHeight); + static const auto fieldDescender = clazz->getField("descender"); + double descender = this->getFieldValue(fieldDescender); + static const auto fieldCapHeight = clazz->getField("capHeight"); + double capHeight = this->getFieldValue(fieldCapHeight); + static const auto fieldAscender = clazz->getField("ascender"); + double ascender = this->getFieldValue(fieldAscender); + static const auto fieldXHeight = clazz->getField("xHeight"); + double xHeight = this->getFieldValue(fieldXHeight); + return TextLayout( + text->toStdString(), + x, + y, + width, + height, + descender, + capHeight, + ascender, + xHeight + ); + } + + public: + /** + * Create a Java/Kotlin-based struct by copying all values from the given C++ struct to Java. + */ + [[maybe_unused]] + static jni::local_ref fromCpp(const TextLayout& value) { + return newInstance( + jni::make_jstring(value.text), + value.x, + value.y, + value.width, + value.height, + value.descender, + value.capHeight, + value.ascender, + value.xHeight + ); + } + }; + +} // namespace margelo::nitro::nitrotext diff --git a/nitrogen/generated/android/c++/JTextLayoutEvent.hpp b/nitrogen/generated/android/c++/JTextLayoutEvent.hpp new file mode 100644 index 0000000..feb6cf0 --- /dev/null +++ b/nitrogen/generated/android/c++/JTextLayoutEvent.hpp @@ -0,0 +1,73 @@ +/// +/// JTextLayoutEvent.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#pragma once + +#include +#include "TextLayoutEvent.hpp" + +#include "JTextLayout.hpp" +#include "TextLayout.hpp" +#include +#include + +namespace margelo::nitro::nitrotext { + + using namespace facebook; + + /** + * The C++ JNI bridge between the C++ struct "TextLayoutEvent" and the the Kotlin data class "TextLayoutEvent". + */ + struct JTextLayoutEvent final: public jni::JavaClass { + public: + static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/nitrotext/TextLayoutEvent;"; + + public: + /** + * Convert this Java/Kotlin-based struct to the C++ struct TextLayoutEvent by copying all values to C++. + */ + [[maybe_unused]] + [[nodiscard]] + TextLayoutEvent toCpp() const { + static const auto clazz = javaClassStatic(); + static const auto fieldLines = clazz->getField>("lines"); + jni::local_ref> lines = this->getFieldValue(fieldLines); + return TextLayoutEvent( + [&]() { + size_t __size = lines->size(); + std::vector __vector; + __vector.reserve(__size); + for (size_t __i = 0; __i < __size; __i++) { + auto __element = lines->getElement(__i); + __vector.push_back(__element->toCpp()); + } + return __vector; + }() + ); + } + + public: + /** + * Create a Java/Kotlin-based struct by copying all values from the given C++ struct to Java. + */ + [[maybe_unused]] + static jni::local_ref fromCpp(const TextLayoutEvent& value) { + return newInstance( + [&]() { + size_t __size = value.lines.size(); + jni::local_ref> __array = jni::JArrayClass::newArray(__size); + for (size_t __i = 0; __i < __size; __i++) { + const auto& __element = value.lines[__i]; + __array->setElement(__i, *JTextLayout::fromCpp(__element)); + } + return __array; + }() + ); + } + }; + +} // namespace margelo::nitro::nitrotext diff --git a/nitrogen/generated/android/c++/JTextTransform.hpp b/nitrogen/generated/android/c++/JTextTransform.hpp new file mode 100644 index 0000000..52b2f17 --- /dev/null +++ b/nitrogen/generated/android/c++/JTextTransform.hpp @@ -0,0 +1,65 @@ +/// +/// JTextTransform.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#pragma once + +#include +#include "TextTransform.hpp" + +namespace margelo::nitro::nitrotext { + + using namespace facebook; + + /** + * The C++ JNI bridge between the C++ enum "TextTransform" and the the Kotlin enum "TextTransform". + */ + struct JTextTransform final: public jni::JavaClass { + public: + static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/nitrotext/TextTransform;"; + + public: + /** + * Convert this Java/Kotlin-based enum to the C++ enum TextTransform. + */ + [[maybe_unused]] + [[nodiscard]] + TextTransform toCpp() const { + static const auto clazz = javaClassStatic(); + static const auto fieldOrdinal = clazz->getField("value"); + int ordinal = this->getFieldValue(fieldOrdinal); + return static_cast(ordinal); + } + + public: + /** + * Create a Java/Kotlin-based enum with the given C++ enum's value. + */ + [[maybe_unused]] + static jni::alias_ref fromCpp(TextTransform value) { + static const auto clazz = javaClassStatic(); + static const auto fieldNONE = clazz->getStaticField("NONE"); + static const auto fieldUPPERCASE = clazz->getStaticField("UPPERCASE"); + static const auto fieldLOWERCASE = clazz->getStaticField("LOWERCASE"); + static const auto fieldCAPITALIZE = clazz->getStaticField("CAPITALIZE"); + + switch (value) { + case TextTransform::NONE: + return clazz->getStaticFieldValue(fieldNONE); + case TextTransform::UPPERCASE: + return clazz->getStaticFieldValue(fieldUPPERCASE); + case TextTransform::LOWERCASE: + return clazz->getStaticFieldValue(fieldLOWERCASE); + case TextTransform::CAPITALIZE: + return clazz->getStaticFieldValue(fieldCAPITALIZE); + default: + std::string stringValue = std::to_string(static_cast(value)); + throw std::invalid_argument("Invalid enum value (" + stringValue + "!"); + } + } + }; + +} // namespace margelo::nitro::nitrotext diff --git a/nitrogen/generated/android/c++/views/JHybridNitroTextStateUpdater.cpp b/nitrogen/generated/android/c++/views/JHybridNitroTextStateUpdater.cpp new file mode 100644 index 0000000..2ce0684 --- /dev/null +++ b/nitrogen/generated/android/c++/views/JHybridNitroTextStateUpdater.cpp @@ -0,0 +1,168 @@ +/// +/// JHybridNitroTextStateUpdater.cpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#include "JHybridNitroTextStateUpdater.hpp" +#include "views/HybridNitroTextComponent.hpp" +#include + +namespace margelo::nitro::nitrotext::views { + +using namespace facebook; +using ConcreteStateData = react::ConcreteState; + +void JHybridNitroTextStateUpdater::updateViewProps(jni::alias_ref /* class */, + jni::alias_ref javaView, + jni::alias_ref stateWrapperInterface) { + JHybridNitroTextSpec* view = javaView->cthis(); + + // Get concrete StateWrapperImpl from passed StateWrapper interface object + jobject rawStateWrapper = stateWrapperInterface.get(); + if (!stateWrapperInterface->isInstanceOf(react::StateWrapperImpl::javaClassStatic())) { + throw std::runtime_error("StateWrapper is not a StateWrapperImpl"); + } + auto stateWrapper = jni::alias_ref{ + static_cast(rawStateWrapper)}; + + std::shared_ptr state = stateWrapper->cthis()->getState(); + auto concreteState = std::dynamic_pointer_cast(state); + const HybridNitroTextState& data = concreteState->getData(); + const std::optional& maybeProps = data.getProps(); + if (!maybeProps.has_value()) { + // Props aren't set yet! + throw std::runtime_error("HybridNitroTextState's data doesn't contain any props!"); + } + const HybridNitroTextProps& props = maybeProps.value(); + if (props.fragments.isDirty) { + view->setFragments(props.fragments.value); + // TODO: Set isDirty = false + } + if (props.selectable.isDirty) { + view->setSelectable(props.selectable.value); + // TODO: Set isDirty = false + } + if (props.allowFontScaling.isDirty) { + view->setAllowFontScaling(props.allowFontScaling.value); + // TODO: Set isDirty = false + } + if (props.ellipsizeMode.isDirty) { + view->setEllipsizeMode(props.ellipsizeMode.value); + // TODO: Set isDirty = false + } + if (props.numberOfLines.isDirty) { + view->setNumberOfLines(props.numberOfLines.value); + // TODO: Set isDirty = false + } + if (props.lineBreakStrategyIOS.isDirty) { + view->setLineBreakStrategyIOS(props.lineBreakStrategyIOS.value); + // TODO: Set isDirty = false + } + if (props.dynamicTypeRamp.isDirty) { + view->setDynamicTypeRamp(props.dynamicTypeRamp.value); + // TODO: Set isDirty = false + } + if (props.maxFontSizeMultiplier.isDirty) { + view->setMaxFontSizeMultiplier(props.maxFontSizeMultiplier.value); + // TODO: Set isDirty = false + } + if (props.adjustsFontSizeToFit.isDirty) { + view->setAdjustsFontSizeToFit(props.adjustsFontSizeToFit.value); + // TODO: Set isDirty = false + } + if (props.minimumFontScale.isDirty) { + view->setMinimumFontScale(props.minimumFontScale.value); + // TODO: Set isDirty = false + } + if (props.onTextLayout.isDirty) { + view->setOnTextLayout(props.onTextLayout.value); + // TODO: Set isDirty = false + } + if (props.onPress.isDirty) { + view->setOnPress(props.onPress.value); + // TODO: Set isDirty = false + } + if (props.onPressIn.isDirty) { + view->setOnPressIn(props.onPressIn.value); + // TODO: Set isDirty = false + } + if (props.onPressOut.isDirty) { + view->setOnPressOut(props.onPressOut.value); + // TODO: Set isDirty = false + } + if (props.text.isDirty) { + view->setText(props.text.value); + // TODO: Set isDirty = false + } + if (props.selectionColor.isDirty) { + view->setSelectionColor(props.selectionColor.value); + // TODO: Set isDirty = false + } + if (props.fontSize.isDirty) { + view->setFontSize(props.fontSize.value); + // TODO: Set isDirty = false + } + if (props.fontWeight.isDirty) { + view->setFontWeight(props.fontWeight.value); + // TODO: Set isDirty = false + } + if (props.fontColor.isDirty) { + view->setFontColor(props.fontColor.value); + // TODO: Set isDirty = false + } + if (props.fragmentBackgroundColor.isDirty) { + view->setFragmentBackgroundColor(props.fragmentBackgroundColor.value); + // TODO: Set isDirty = false + } + if (props.fontStyle.isDirty) { + view->setFontStyle(props.fontStyle.value); + // TODO: Set isDirty = false + } + if (props.fontFamily.isDirty) { + view->setFontFamily(props.fontFamily.value); + // TODO: Set isDirty = false + } + if (props.lineHeight.isDirty) { + view->setLineHeight(props.lineHeight.value); + // TODO: Set isDirty = false + } + if (props.letterSpacing.isDirty) { + view->setLetterSpacing(props.letterSpacing.value); + // TODO: Set isDirty = false + } + if (props.textAlign.isDirty) { + view->setTextAlign(props.textAlign.value); + // TODO: Set isDirty = false + } + if (props.textTransform.isDirty) { + view->setTextTransform(props.textTransform.value); + // TODO: Set isDirty = false + } + if (props.textDecorationLine.isDirty) { + view->setTextDecorationLine(props.textDecorationLine.value); + // TODO: Set isDirty = false + } + if (props.textDecorationColor.isDirty) { + view->setTextDecorationColor(props.textDecorationColor.value); + // TODO: Set isDirty = false + } + if (props.textDecorationStyle.isDirty) { + view->setTextDecorationStyle(props.textDecorationStyle.value); + // TODO: Set isDirty = false + } + + // Update hybridRef if it changed + if (props.hybridRef.isDirty) { + // hybridRef changed - call it with new this + const auto& maybeFunc = props.hybridRef.value; + if (maybeFunc.has_value()) { + std::shared_ptr shared = javaView->cthis()->shared_cast(); + maybeFunc.value()(shared); + } + // TODO: Set isDirty = false + } +} + +} // namespace margelo::nitro::nitrotext::views diff --git a/nitrogen/generated/android/c++/views/JHybridNitroTextStateUpdater.hpp b/nitrogen/generated/android/c++/views/JHybridNitroTextStateUpdater.hpp new file mode 100644 index 0000000..c102d50 --- /dev/null +++ b/nitrogen/generated/android/c++/views/JHybridNitroTextStateUpdater.hpp @@ -0,0 +1,49 @@ +/// +/// JHybridNitroTextStateUpdater.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#pragma once + +#ifndef RN_SERIALIZABLE_STATE +#error NitroText was compiled without the 'RN_SERIALIZABLE_STATE' flag. This flag is required for Nitro Views - set it in your CMakeLists! +#endif + +#include +#include +#include +#include +#include +#include +#include "JHybridNitroTextSpec.hpp" +#include "views/HybridNitroTextComponent.hpp" + +namespace margelo::nitro::nitrotext::views { + +using namespace facebook; + +class JHybridNitroTextStateUpdater: public jni::JavaClass { +public: + static constexpr auto kJavaDescriptor = "Lcom/margelo/nitro/nitrotext/views/HybridNitroTextStateUpdater;"; + +public: + static void updateViewProps(jni::alias_ref /* class */, + jni::alias_ref view, + jni::alias_ref stateWrapperInterface); + +public: + static void registerNatives() { + // Register JNI calls + javaClassStatic()->registerNatives({ + makeNativeMethod("updateViewProps", JHybridNitroTextStateUpdater::updateViewProps), + }); + // Register React Native view component descriptor + auto provider = react::concreteComponentDescriptorProvider(); + auto providerRegistry = react::CoreComponentsRegistry::sharedProviderRegistry(); + providerRegistry->add(provider); + } +}; + +} // namespace margelo::nitro::nitrotext::views diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/DynamicTypeRamp.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/DynamicTypeRamp.kt new file mode 100644 index 0000000..efc03bb --- /dev/null +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/DynamicTypeRamp.kt @@ -0,0 +1,30 @@ +/// +/// DynamicTypeRamp.kt +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +package com.margelo.nitro.nitrotext + +import androidx.annotation.Keep +import com.facebook.proguard.annotations.DoNotStrip + +/** + * Represents the JavaScript enum/union "DynamicTypeRamp". + */ +@DoNotStrip +@Keep +enum class DynamicTypeRamp(@DoNotStrip @Keep val value: Int) { + CAPTION2(0), + CAPTION1(1), + FOOTNOTE(2), + SUBHEADLINE(3), + CALLOUT(4), + BODY(5), + HEADLINE(6), + TITLE3(7), + TITLE2(8), + TITLE1(9), + LARGETITLE(10); +} diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/EllipsizeMode.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/EllipsizeMode.kt new file mode 100644 index 0000000..6d91a05 --- /dev/null +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/EllipsizeMode.kt @@ -0,0 +1,23 @@ +/// +/// EllipsizeMode.kt +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +package com.margelo.nitro.nitrotext + +import androidx.annotation.Keep +import com.facebook.proguard.annotations.DoNotStrip + +/** + * Represents the JavaScript enum/union "EllipsizeMode". + */ +@DoNotStrip +@Keep +enum class EllipsizeMode(@DoNotStrip @Keep val value: Int) { + HEAD(0), + MIDDLE(1), + TAIL(2), + CLIP(3); +} diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/FontStyle.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/FontStyle.kt new file mode 100644 index 0000000..a848fc2 --- /dev/null +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/FontStyle.kt @@ -0,0 +1,22 @@ +/// +/// FontStyle.kt +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +package com.margelo.nitro.nitrotext + +import androidx.annotation.Keep +import com.facebook.proguard.annotations.DoNotStrip + +/** + * Represents the JavaScript enum/union "FontStyle". + */ +@DoNotStrip +@Keep +enum class FontStyle(@DoNotStrip @Keep val value: Int) { + NORMAL(0), + ITALIC(1), + OBLIQUE(2); +} diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/FontWeight.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/FontWeight.kt new file mode 100644 index 0000000..85db768 --- /dev/null +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/FontWeight.kt @@ -0,0 +1,31 @@ +/// +/// FontWeight.kt +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +package com.margelo.nitro.nitrotext + +import androidx.annotation.Keep +import com.facebook.proguard.annotations.DoNotStrip + +/** + * Represents the JavaScript enum/union "FontWeight". + */ +@DoNotStrip +@Keep +enum class FontWeight(@DoNotStrip @Keep val value: Int) { + NORMAL(0), + BOLD(1), + ULTRALIGHT(2), + THIN(3), + LIGHT(4), + MEDIUM(5), + REGULAR(6), + SEMIBOLD(7), + CONDENSEDBOLD(8), + CONDENSED(9), + HEAVY(10), + BLACK(11); +} diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/Fragment.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/Fragment.kt new file mode 100644 index 0000000..34e46a1 --- /dev/null +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/Fragment.kt @@ -0,0 +1,71 @@ +/// +/// Fragment.kt +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +package com.margelo.nitro.nitrotext + +import androidx.annotation.Keep +import com.facebook.proguard.annotations.DoNotStrip +import com.margelo.nitro.core.* + + +/** + * Represents the JavaScript object/struct "Fragment". + */ +@DoNotStrip +@Keep +data class Fragment + @DoNotStrip + @Keep + constructor( + @DoNotStrip + @Keep + val text: String?, + @DoNotStrip + @Keep + val selectionColor: String?, + @DoNotStrip + @Keep + val fontSize: Double?, + @DoNotStrip + @Keep + val fontWeight: FontWeight?, + @DoNotStrip + @Keep + val fontColor: String?, + @DoNotStrip + @Keep + val fragmentBackgroundColor: String?, + @DoNotStrip + @Keep + val fontStyle: FontStyle?, + @DoNotStrip + @Keep + val fontFamily: String?, + @DoNotStrip + @Keep + val lineHeight: Double?, + @DoNotStrip + @Keep + val letterSpacing: Double?, + @DoNotStrip + @Keep + val textAlign: TextAlign?, + @DoNotStrip + @Keep + val textTransform: TextTransform?, + @DoNotStrip + @Keep + val textDecorationLine: TextDecorationLine?, + @DoNotStrip + @Keep + val textDecorationColor: String?, + @DoNotStrip + @Keep + val textDecorationStyle: TextDecorationStyle? + ) { + /* main constructor */ +} diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/Func_void.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/Func_void.kt new file mode 100644 index 0000000..63db75b --- /dev/null +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/Func_void.kt @@ -0,0 +1,81 @@ +/// +/// Func_void.kt +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +package com.margelo.nitro.nitrotext + +import androidx.annotation.Keep +import com.facebook.jni.HybridData +import com.facebook.proguard.annotations.DoNotStrip +import com.margelo.nitro.core.* +import dalvik.annotation.optimization.FastNative + + +/** + * Represents the JavaScript callback `() => void`. + * This can be either implemented in C++ (in which case it might be a callback coming from JS), + * or in Kotlin/Java (in which case it is a native callback). + */ +@DoNotStrip +@Keep +@Suppress("ClassName", "RedundantUnitReturnType") +fun interface Func_void: () -> Unit { + /** + * Call the given JS callback. + * @throws Throwable if the JS function itself throws an error, or if the JS function/runtime has already been deleted. + */ + @DoNotStrip + @Keep + override fun invoke(): Unit +} + +/** + * Represents the JavaScript callback `() => void`. + * This is implemented in C++, via a `std::function<...>`. + * The callback might be coming from JS. + */ +@DoNotStrip +@Keep +@Suppress( + "KotlinJniMissingFunction", "unused", + "RedundantSuppression", "RedundantUnitReturnType", "FunctionName", + "ConvertSecondaryConstructorToPrimary", "ClassName", "LocalVariableName", +) +class Func_void_cxx: Func_void { + @DoNotStrip + @Keep + private val mHybridData: HybridData + + @DoNotStrip + @Keep + private constructor(hybridData: HybridData) { + mHybridData = hybridData + } + + @DoNotStrip + @Keep + override fun invoke(): Unit + = invoke_cxx() + + @FastNative + private external fun invoke_cxx(): Unit +} + +/** + * Represents the JavaScript callback `() => void`. + * This is implemented in Java/Kotlin, via a `() -> Unit`. + * The callback is always coming from native. + */ +@DoNotStrip +@Keep +@Suppress("ClassName", "RedundantUnitReturnType", "unused") +class Func_void_java(private val function: () -> Unit): Func_void { + @DoNotStrip + @Keep + override fun invoke(): Unit { + return this.function() + } +} diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/Func_void_TextLayoutEvent.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/Func_void_TextLayoutEvent.kt new file mode 100644 index 0000000..8faef9b --- /dev/null +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/Func_void_TextLayoutEvent.kt @@ -0,0 +1,81 @@ +/// +/// Func_void_TextLayoutEvent.kt +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +package com.margelo.nitro.nitrotext + +import androidx.annotation.Keep +import com.facebook.jni.HybridData +import com.facebook.proguard.annotations.DoNotStrip +import com.margelo.nitro.core.* +import dalvik.annotation.optimization.FastNative + + +/** + * Represents the JavaScript callback `(layout: struct) => void`. + * This can be either implemented in C++ (in which case it might be a callback coming from JS), + * or in Kotlin/Java (in which case it is a native callback). + */ +@DoNotStrip +@Keep +@Suppress("ClassName", "RedundantUnitReturnType") +fun interface Func_void_TextLayoutEvent: (TextLayoutEvent) -> Unit { + /** + * Call the given JS callback. + * @throws Throwable if the JS function itself throws an error, or if the JS function/runtime has already been deleted. + */ + @DoNotStrip + @Keep + override fun invoke(layout: TextLayoutEvent): Unit +} + +/** + * Represents the JavaScript callback `(layout: struct) => void`. + * This is implemented in C++, via a `std::function<...>`. + * The callback might be coming from JS. + */ +@DoNotStrip +@Keep +@Suppress( + "KotlinJniMissingFunction", "unused", + "RedundantSuppression", "RedundantUnitReturnType", "FunctionName", + "ConvertSecondaryConstructorToPrimary", "ClassName", "LocalVariableName", +) +class Func_void_TextLayoutEvent_cxx: Func_void_TextLayoutEvent { + @DoNotStrip + @Keep + private val mHybridData: HybridData + + @DoNotStrip + @Keep + private constructor(hybridData: HybridData) { + mHybridData = hybridData + } + + @DoNotStrip + @Keep + override fun invoke(layout: TextLayoutEvent): Unit + = invoke_cxx(layout) + + @FastNative + private external fun invoke_cxx(layout: TextLayoutEvent): Unit +} + +/** + * Represents the JavaScript callback `(layout: struct) => void`. + * This is implemented in Java/Kotlin, via a `(TextLayoutEvent) -> Unit`. + * The callback is always coming from native. + */ +@DoNotStrip +@Keep +@Suppress("ClassName", "RedundantUnitReturnType", "unused") +class Func_void_TextLayoutEvent_java(private val function: (TextLayoutEvent) -> Unit): Func_void_TextLayoutEvent { + @DoNotStrip + @Keep + override fun invoke(layout: TextLayoutEvent): Unit { + return this.function(layout) + } +} diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/HybridNitroTextSpec.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/HybridNitroTextSpec.kt new file mode 100644 index 0000000..88ddc86 --- /dev/null +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/HybridNitroTextSpec.kt @@ -0,0 +1,255 @@ +/// +/// HybridNitroTextSpec.kt +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +package com.margelo.nitro.nitrotext + +import androidx.annotation.Keep +import com.facebook.jni.HybridData +import com.facebook.proguard.annotations.DoNotStrip +import com.margelo.nitro.core.* +import com.margelo.nitro.views.* + +/** + * A Kotlin class representing the NitroText HybridObject. + * Implement this abstract class to create Kotlin-based instances of NitroText. + */ +@DoNotStrip +@Keep +@Suppress( + "KotlinJniMissingFunction", "unused", + "RedundantSuppression", "RedundantUnitReturnType", "SimpleRedundantLet", + "LocalVariableName", "PropertyName", "PrivatePropertyName", "FunctionName" +) +abstract class HybridNitroTextSpec: HybridView() { + @DoNotStrip + private var mHybridData: HybridData = initHybrid() + + init { + super.updateNative(mHybridData) + } + + override fun updateNative(hybridData: HybridData) { + mHybridData = hybridData + super.updateNative(hybridData) + } + + // Properties + @get:DoNotStrip + @get:Keep + @set:DoNotStrip + @set:Keep + abstract var fragments: Array? + + @get:DoNotStrip + @get:Keep + @set:DoNotStrip + @set:Keep + abstract var selectable: Boolean? + + @get:DoNotStrip + @get:Keep + @set:DoNotStrip + @set:Keep + abstract var allowFontScaling: Boolean? + + @get:DoNotStrip + @get:Keep + @set:DoNotStrip + @set:Keep + abstract var ellipsizeMode: EllipsizeMode? + + @get:DoNotStrip + @get:Keep + @set:DoNotStrip + @set:Keep + abstract var numberOfLines: Double? + + @get:DoNotStrip + @get:Keep + @set:DoNotStrip + @set:Keep + abstract var lineBreakStrategyIOS: LineBreakStrategyIOS? + + @get:DoNotStrip + @get:Keep + @set:DoNotStrip + @set:Keep + abstract var dynamicTypeRamp: DynamicTypeRamp? + + @get:DoNotStrip + @get:Keep + @set:DoNotStrip + @set:Keep + abstract var maxFontSizeMultiplier: Double? + + @get:DoNotStrip + @get:Keep + @set:DoNotStrip + @set:Keep + abstract var adjustsFontSizeToFit: Boolean? + + @get:DoNotStrip + @get:Keep + @set:DoNotStrip + @set:Keep + abstract var minimumFontScale: Double? + + abstract var onTextLayout: ((layout: TextLayoutEvent) -> Unit)? + + private var onTextLayout_cxx: Func_void_TextLayoutEvent? + @Keep + @DoNotStrip + get() { + return onTextLayout?.let { Func_void_TextLayoutEvent_java(it) } + } + @Keep + @DoNotStrip + set(value) { + onTextLayout = value?.let { it } + } + + abstract var onPress: (() -> Unit)? + + private var onPress_cxx: Func_void? + @Keep + @DoNotStrip + get() { + return onPress?.let { Func_void_java(it) } + } + @Keep + @DoNotStrip + set(value) { + onPress = value?.let { it } + } + + abstract var onPressIn: (() -> Unit)? + + private var onPressIn_cxx: Func_void? + @Keep + @DoNotStrip + get() { + return onPressIn?.let { Func_void_java(it) } + } + @Keep + @DoNotStrip + set(value) { + onPressIn = value?.let { it } + } + + abstract var onPressOut: (() -> Unit)? + + private var onPressOut_cxx: Func_void? + @Keep + @DoNotStrip + get() { + return onPressOut?.let { Func_void_java(it) } + } + @Keep + @DoNotStrip + set(value) { + onPressOut = value?.let { it } + } + + @get:DoNotStrip + @get:Keep + @set:DoNotStrip + @set:Keep + abstract var text: String? + + @get:DoNotStrip + @get:Keep + @set:DoNotStrip + @set:Keep + abstract var selectionColor: String? + + @get:DoNotStrip + @get:Keep + @set:DoNotStrip + @set:Keep + abstract var fontSize: Double? + + @get:DoNotStrip + @get:Keep + @set:DoNotStrip + @set:Keep + abstract var fontWeight: FontWeight? + + @get:DoNotStrip + @get:Keep + @set:DoNotStrip + @set:Keep + abstract var fontColor: String? + + @get:DoNotStrip + @get:Keep + @set:DoNotStrip + @set:Keep + abstract var fragmentBackgroundColor: String? + + @get:DoNotStrip + @get:Keep + @set:DoNotStrip + @set:Keep + abstract var fontStyle: FontStyle? + + @get:DoNotStrip + @get:Keep + @set:DoNotStrip + @set:Keep + abstract var fontFamily: String? + + @get:DoNotStrip + @get:Keep + @set:DoNotStrip + @set:Keep + abstract var lineHeight: Double? + + @get:DoNotStrip + @get:Keep + @set:DoNotStrip + @set:Keep + abstract var letterSpacing: Double? + + @get:DoNotStrip + @get:Keep + @set:DoNotStrip + @set:Keep + abstract var textAlign: TextAlign? + + @get:DoNotStrip + @get:Keep + @set:DoNotStrip + @set:Keep + abstract var textTransform: TextTransform? + + @get:DoNotStrip + @get:Keep + @set:DoNotStrip + @set:Keep + abstract var textDecorationLine: TextDecorationLine? + + @get:DoNotStrip + @get:Keep + @set:DoNotStrip + @set:Keep + abstract var textDecorationColor: String? + + @get:DoNotStrip + @get:Keep + @set:DoNotStrip + @set:Keep + abstract var textDecorationStyle: TextDecorationStyle? + + // Methods + + + private external fun initHybrid(): HybridData + + companion object { + private const val TAG = "HybridNitroTextSpec" + } +} diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/LineBreakStrategyIOS.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/LineBreakStrategyIOS.kt new file mode 100644 index 0000000..16cef11 --- /dev/null +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/LineBreakStrategyIOS.kt @@ -0,0 +1,23 @@ +/// +/// LineBreakStrategyIOS.kt +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +package com.margelo.nitro.nitrotext + +import androidx.annotation.Keep +import com.facebook.proguard.annotations.DoNotStrip + +/** + * Represents the JavaScript enum/union "LineBreakStrategyIOS". + */ +@DoNotStrip +@Keep +enum class LineBreakStrategyIOS(@DoNotStrip @Keep val value: Int) { + NONE(0), + STANDARD(1), + HANGUL_WORD(2), + PUSH_OUT(3); +} diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/NitroTextOnLoad.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/NitroTextOnLoad.kt new file mode 100644 index 0000000..c88d798 --- /dev/null +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/NitroTextOnLoad.kt @@ -0,0 +1,35 @@ +/// +/// NitroTextOnLoad.kt +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +package com.margelo.nitro.nitrotext + +import android.util.Log + +internal class NitroTextOnLoad { + companion object { + private const val TAG = "NitroTextOnLoad" + private var didLoad = false + /** + * Initializes the native part of "NitroText". + * This method is idempotent and can be called more than once. + */ + @JvmStatic + fun initializeNative() { + if (didLoad) return + try { + Log.i(TAG, "Loading NitroText C++ library...") + System.loadLibrary("NitroText") + Log.i(TAG, "Successfully loaded NitroText C++ library!") + didLoad = true + } catch (e: Error) { + Log.e(TAG, "Failed to load NitroText C++ library! Is it properly installed and linked? " + + "Is the name correct? (see `CMakeLists.txt`, at `add_library(...)`)", e) + throw e + } + } + } +} diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/TextAlign.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/TextAlign.kt new file mode 100644 index 0000000..92daeba --- /dev/null +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/TextAlign.kt @@ -0,0 +1,24 @@ +/// +/// TextAlign.kt +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +package com.margelo.nitro.nitrotext + +import androidx.annotation.Keep +import com.facebook.proguard.annotations.DoNotStrip + +/** + * Represents the JavaScript enum/union "TextAlign". + */ +@DoNotStrip +@Keep +enum class TextAlign(@DoNotStrip @Keep val value: Int) { + AUTO(0), + LEFT(1), + RIGHT(2), + CENTER(3), + JUSTIFY(4); +} diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/TextDecorationLine.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/TextDecorationLine.kt new file mode 100644 index 0000000..654ac33 --- /dev/null +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/TextDecorationLine.kt @@ -0,0 +1,23 @@ +/// +/// TextDecorationLine.kt +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +package com.margelo.nitro.nitrotext + +import androidx.annotation.Keep +import com.facebook.proguard.annotations.DoNotStrip + +/** + * Represents the JavaScript enum/union "TextDecorationLine". + */ +@DoNotStrip +@Keep +enum class TextDecorationLine(@DoNotStrip @Keep val value: Int) { + NONE(0), + UNDERLINE(1), + LINE_THROUGH(2), + UNDERLINE_LINE_THROUGH(3); +} diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/TextDecorationStyle.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/TextDecorationStyle.kt new file mode 100644 index 0000000..28bba8e --- /dev/null +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/TextDecorationStyle.kt @@ -0,0 +1,23 @@ +/// +/// TextDecorationStyle.kt +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +package com.margelo.nitro.nitrotext + +import androidx.annotation.Keep +import com.facebook.proguard.annotations.DoNotStrip + +/** + * Represents the JavaScript enum/union "TextDecorationStyle". + */ +@DoNotStrip +@Keep +enum class TextDecorationStyle(@DoNotStrip @Keep val value: Int) { + SOLID(0), + DOUBLE(1), + DOTTED(2), + DASHED(3); +} diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/TextLayout.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/TextLayout.kt new file mode 100644 index 0000000..7866d8f --- /dev/null +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/TextLayout.kt @@ -0,0 +1,53 @@ +/// +/// TextLayout.kt +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +package com.margelo.nitro.nitrotext + +import androidx.annotation.Keep +import com.facebook.proguard.annotations.DoNotStrip +import com.margelo.nitro.core.* + + +/** + * Represents the JavaScript object/struct "TextLayout". + */ +@DoNotStrip +@Keep +data class TextLayout + @DoNotStrip + @Keep + constructor( + @DoNotStrip + @Keep + val text: String, + @DoNotStrip + @Keep + val x: Double, + @DoNotStrip + @Keep + val y: Double, + @DoNotStrip + @Keep + val width: Double, + @DoNotStrip + @Keep + val height: Double, + @DoNotStrip + @Keep + val descender: Double, + @DoNotStrip + @Keep + val capHeight: Double, + @DoNotStrip + @Keep + val ascender: Double, + @DoNotStrip + @Keep + val xHeight: Double + ) { + /* main constructor */ +} diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/TextLayoutEvent.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/TextLayoutEvent.kt new file mode 100644 index 0000000..90b29ff --- /dev/null +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/TextLayoutEvent.kt @@ -0,0 +1,29 @@ +/// +/// TextLayoutEvent.kt +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +package com.margelo.nitro.nitrotext + +import androidx.annotation.Keep +import com.facebook.proguard.annotations.DoNotStrip +import com.margelo.nitro.core.* + + +/** + * Represents the JavaScript object/struct "TextLayoutEvent". + */ +@DoNotStrip +@Keep +data class TextLayoutEvent + @DoNotStrip + @Keep + constructor( + @DoNotStrip + @Keep + val lines: Array + ) { + /* main constructor */ +} diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/TextTransform.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/TextTransform.kt new file mode 100644 index 0000000..3c76277 --- /dev/null +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/TextTransform.kt @@ -0,0 +1,23 @@ +/// +/// TextTransform.kt +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +package com.margelo.nitro.nitrotext + +import androidx.annotation.Keep +import com.facebook.proguard.annotations.DoNotStrip + +/** + * Represents the JavaScript enum/union "TextTransform". + */ +@DoNotStrip +@Keep +enum class TextTransform(@DoNotStrip @Keep val value: Int) { + NONE(0), + UPPERCASE(1), + LOWERCASE(2), + CAPITALIZE(3); +} diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/views/HybridNitroTextManager.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/views/HybridNitroTextManager.kt new file mode 100644 index 0000000..7a5695c --- /dev/null +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/views/HybridNitroTextManager.kt @@ -0,0 +1,50 @@ +/// +/// HybridNitroTextManager.kt +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +package com.margelo.nitro.nitrotext.views + +import android.view.View +import com.facebook.react.uimanager.ReactStylesDiffMap +import com.facebook.react.uimanager.SimpleViewManager +import com.facebook.react.uimanager.StateWrapper +import com.facebook.react.uimanager.ThemedReactContext +import com.nitrotext.* + +/** + * Represents the React Native `ViewManager` for the "NitroText" Nitro HybridView. + */ +open class HybridNitroTextManager: SimpleViewManager() { + private val views = hashMapOf() + + override fun getName(): String { + return "NitroText" + } + + override fun createViewInstance(reactContext: ThemedReactContext): View { + val hybridView = HybridNitroText(reactContext) + val view = hybridView.view + views[view] = hybridView + return view + } + + override fun onDropViewInstance(view: View) { + super.onDropViewInstance(view) + views.remove(view) + } + + override fun updateState(view: View, props: ReactStylesDiffMap, stateWrapper: StateWrapper): Any? { + val hybridView = views[view] ?: throw Error("Couldn't find view $view in local views table!") + + // 1. Update each prop individually + hybridView.beforeUpdate() + HybridNitroTextStateUpdater.updateViewProps(hybridView, stateWrapper) + hybridView.afterUpdate() + + // 2. Continue in base View props + return super.updateState(view, props, stateWrapper) + } +} diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/views/HybridNitroTextStateUpdater.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/views/HybridNitroTextStateUpdater.kt new file mode 100644 index 0000000..399877d --- /dev/null +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/views/HybridNitroTextStateUpdater.kt @@ -0,0 +1,23 @@ +/// +/// HybridNitroTextStateUpdater.kt +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +package com.margelo.nitro.nitrotext.views + +import com.facebook.react.uimanager.StateWrapper +import com.margelo.nitro.nitrotext.* + +internal class HybridNitroTextStateUpdater { + companion object { + /** + * Updates the props for [view] through C++. + * The [state] prop is expected to contain [view]'s props as wrapped Fabric state. + */ + @Suppress("KotlinJniMissingFunction") + @JvmStatic + external fun updateViewProps(view: HybridNitroTextSpec, state: StateWrapper) + } +} diff --git a/nitrogen/generated/shared/c++/views/HybridNitroTextComponent.hpp b/nitrogen/generated/shared/c++/views/HybridNitroTextComponent.hpp index b811738..d7431c9 100644 --- a/nitrogen/generated/shared/c++/views/HybridNitroTextComponent.hpp +++ b/nitrogen/generated/shared/c++/views/HybridNitroTextComponent.hpp @@ -19,58 +19,20 @@ #include "Fragment.hpp" #include #include -#include -#include #include "EllipsizeMode.hpp" -#include -#include #include "LineBreakStrategyIOS.hpp" -#include #include "DynamicTypeRamp.hpp" -#include -#include -#include -#include #include "TextLayoutEvent.hpp" #include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include -#include -#include #include "FontWeight.hpp" -#include -#include -#include -#include -#include #include "FontStyle.hpp" -#include -#include -#include -#include -#include #include "TextAlign.hpp" -#include #include "TextTransform.hpp" -#include #include "TextDecorationLine.hpp" -#include -#include -#include #include "TextDecorationStyle.hpp" -#include #include #include "HybridNitroTextSpec.hpp" -#include -#include namespace margelo::nitro::nitrotext::views { diff --git a/package.json b/package.json index cba0893..1af0515 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "clean": "git clean -dfX", "release": "semantic-release", "build": "bun run typecheck && bob build", - "codegen": "nitro-codegen --logLevel=\"debug\" && bun run build", + "codegen": "nitrogen --logLevel=\"debug\" && bun run build && node post-script.js", "postcodegen": "bun --cwd example pod" }, "keywords": [ @@ -59,12 +59,12 @@ "@semantic-release/git": "^10.0.1", "@types/jest": "^29.5.12", "@types/react": "19.1.0", + "conventional-changelog-conventionalcommits": "^9.1.0", "nitrogen": "^0.29.6", "react": "19.1.0", "react-native": "0.81.4", "react-native-builder-bob": "^0.37.0", "react-native-nitro-modules": "^0.29.6", - "conventional-changelog-conventionalcommits": "^9.1.0", "semantic-release": "^24.2.8", "typescript": "^5.8.3" }, diff --git a/post-script.js b/post-script.js new file mode 100644 index 0000000..0202df9 --- /dev/null +++ b/post-script.js @@ -0,0 +1,32 @@ +/** +* @file This script is auto-generated by create-nitro-module and should not be edited. +* +* @description This script applies a workaround for Android by modifying the 'OnLoad.cpp' file. +* It reads the file content and removes the 'margelo/nitro/' string from it. This enables support for custom package names. +* +* @module create-nitro-module +*/ +const path = require('node:path') +const { writeFile, readFile } = require('node:fs/promises') + +const androidWorkaround = async () => { + const androidOnLoadFile = path.join( + process.cwd(), + 'nitrogen/generated/android', + 'NitroTextOnLoad.cpp' + ) + + const viewManagerFile = path.join( + process.cwd(), + 'nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/views', + 'HybridNitroTextManager.kt' + ) + + const viewManagerStr = await readFile(viewManagerFile, { encoding: 'utf8' }) + await writeFile(viewManagerFile, viewManagerStr.replace(/com\.margelo\.nitro\.nitrotext\.\*/g, 'com.nitrotext.*')) + + + const str = await readFile(androidOnLoadFile, { encoding: 'utf8' }) + await writeFile(androidOnLoadFile, str.replace(/margelo\/nitro\//g, '')) +} +androidWorkaround() \ No newline at end of file diff --git a/src/nitro-text.tsx b/src/nitro-text.tsx index 261982a..e462713 100644 --- a/src/nitro-text.tsx +++ b/src/nitro-text.tsx @@ -42,7 +42,7 @@ export const NitroText = (props: NitroTextPropsWithEvents) => { const { children, style, - selectable = true, + selectable, selectionColor, onTextLayout, onPress, @@ -60,7 +60,7 @@ export const NitroText = (props: NitroTextPropsWithEvents) => { return flattenChildrenToFragments(children, style as any) }, [children, style, isSimpleText]) - if (isInsideRNText || Platform.OS === 'android') { + if (isInsideRNText) { const onRNTextLayout = useCallback( (e: TextLayoutEvent) => { onTextLayout?.(e.nativeEvent) @@ -124,8 +124,10 @@ export const NitroText = (props: NitroTextPropsWithEvents) => { selectionColor={selectionColor as string} style={style} fontColor={topStyles.fontColor} + fontSize={topStyles.fontSize} fontWeight={topStyles.fontWeight} fontStyle={topStyles.fontStyle} + lineHeight={topStyles.lineHeight} letterSpacing={topStyles.letterSpacing} textAlign={topStyles.textAlign} textTransform={topStyles.textTransform} diff --git a/src/specs/nitro-text.nitro.ts b/src/specs/nitro-text.nitro.ts index d3129cd..84178a3 100755 --- a/src/specs/nitro-text.nitro.ts +++ b/src/specs/nitro-text.nitro.ts @@ -99,5 +99,5 @@ export interface NitroTextMethods extends HybridViewMethods { } export type NitroText = HybridView< NitroTextProps, NitroTextMethods, - { ios: 'swift' } + { ios: 'swift', android: 'kotlin' } > From 018b86b3709ebfcca648677fadbfaa68abf2e604 Mon Sep 17 00:00:00 2001 From: Patrick Kabwe Date: Mon, 29 Sep 2025 08:06:17 +0200 Subject: [PATCH 02/12] feat: add support for `onTextLayout` --- .../java/com/nitrotext/HybridNitroText.kt | 18 ++-- ...neHeightSpan.kt => NitroLineHeightSpan.kt} | 2 +- .../main/java/com/nitrotext/NitroTextImpl.kt | 3 +- .../main/java/com/nitrotext/NitroTextView.kt | 87 ++++++++++++++++++- cpp/NitroTextShadowNode.cpp | 22 +++-- example/App.tsx | 3 +- 6 files changed, 115 insertions(+), 20 deletions(-) rename android/src/main/java/com/nitrotext/{NitroTextImpl+LineHeightSpan.kt => NitroLineHeightSpan.kt} (91%) diff --git a/android/src/main/java/com/nitrotext/HybridNitroText.kt b/android/src/main/java/com/nitrotext/HybridNitroText.kt index 11e2802..74cd3b5 100755 --- a/android/src/main/java/com/nitrotext/HybridNitroText.kt +++ b/android/src/main/java/com/nitrotext/HybridNitroText.kt @@ -1,18 +1,20 @@ package com.nitrotext -import android.os.Build -import android.view.View import androidx.annotation.Keep -import androidx.annotation.RequiresApi import com.facebook.proguard.annotations.DoNotStrip import com.facebook.react.uimanager.ThemedReactContext import com.margelo.nitro.nitrotext.* @Keep @DoNotStrip -class HybridNitroText(val context: ThemedReactContext) : HybridNitroTextSpec() { +class HybridNitroText(val context: ThemedReactContext) : HybridNitroTextSpec(), NitroTextViewDelegate { override val view: NitroTextView = NitroTextView(context) private val impl = NitroTextImpl(view.textView) + private var onTextLayoutCallback: ((TextLayoutEvent) -> Unit)? = null + + init { + view.nitroTextDelegate = this + } override var fragments: Array? get() = null @@ -73,9 +75,9 @@ class HybridNitroText(val context: ThemedReactContext) : HybridNitroTextSpec() { set(value) { value } override var onTextLayout: ((TextLayoutEvent) -> Unit)? - get() = null + get() = onTextLayoutCallback set(value) { - // TODO: Implement onTextLayout + onTextLayoutCallback = value } override var onPress: (() -> Unit)? @@ -169,4 +171,8 @@ class HybridNitroText(val context: ThemedReactContext) : HybridNitroTextSpec() { view.requestLayout() view.invalidate() } + + override fun onNitroTextLayout(event: TextLayoutEvent) { + onTextLayoutCallback?.invoke(event) + } } diff --git a/android/src/main/java/com/nitrotext/NitroTextImpl+LineHeightSpan.kt b/android/src/main/java/com/nitrotext/NitroLineHeightSpan.kt similarity index 91% rename from android/src/main/java/com/nitrotext/NitroTextImpl+LineHeightSpan.kt rename to android/src/main/java/com/nitrotext/NitroLineHeightSpan.kt index 15d1fbd..cc1487a 100644 --- a/android/src/main/java/com/nitrotext/NitroTextImpl+LineHeightSpan.kt +++ b/android/src/main/java/com/nitrotext/NitroLineHeightSpan.kt @@ -8,7 +8,7 @@ import kotlin.math.floor /** * Matches React Native's CustomLineHeightSpan so lineHeight behaves the same as . */ -class NitroTextImplLineHeightSpan(heightPx: Float) : LineHeightSpan { +class NitroLineHeightSpan(heightPx: Float) : LineHeightSpan { private val lineHeight: Int = ceil(heightPx.toDouble()).toInt() override fun chooseHeight( diff --git a/android/src/main/java/com/nitrotext/NitroTextImpl.kt b/android/src/main/java/com/nitrotext/NitroTextImpl.kt index 902f8e2..02efe2d 100644 --- a/android/src/main/java/com/nitrotext/NitroTextImpl.kt +++ b/android/src/main/java/com/nitrotext/NitroTextImpl.kt @@ -11,7 +11,6 @@ import android.text.style.StrikethroughSpan import android.text.style.StyleSpan import android.text.style.TypefaceSpan import android.text.style.UnderlineSpan -import android.util.Log import android.view.Gravity import androidx.appcompat.widget.AppCompatTextView import com.facebook.react.uimanager.PixelUtil @@ -280,7 +279,7 @@ class NitroTextImpl(private val view: AppCompatTextView) { } else { Spannable.SPAN_EXCLUSIVE_INCLUSIVE } - builder.setSpan(NitroTextImplLineHeightSpan(lineHeightPx), start, end, flags) + builder.setSpan(NitroLineHeightSpan(lineHeightPx), start, end, flags) } companion object { diff --git a/android/src/main/java/com/nitrotext/NitroTextView.kt b/android/src/main/java/com/nitrotext/NitroTextView.kt index 7847fa6..063540a 100644 --- a/android/src/main/java/com/nitrotext/NitroTextView.kt +++ b/android/src/main/java/com/nitrotext/NitroTextView.kt @@ -2,14 +2,23 @@ package com.nitrotext import android.annotation.SuppressLint import android.content.Context +import android.graphics.Rect import android.graphics.text.LineBreaker import android.text.Layout +import android.text.TextPaint import android.view.View import androidx.appcompat.widget.AppCompatTextView import com.facebook.react.views.view.ReactViewGroup +import com.margelo.nitro.nitrotext.TextLayout +import com.margelo.nitro.nitrotext.TextLayoutEvent + + +interface NitroTextViewDelegate { + fun onNitroTextLayout(event: TextLayoutEvent) +} @SuppressLint("ViewConstructor") -class NitroTextView(ctx: Context) : ReactViewGroup(ctx){ +class NitroTextView(ctx: Context) : ReactViewGroup(ctx) { val textView = AppCompatTextView(ctx).apply { includeFontPadding = false minWidth = 0; minHeight = 0 @@ -18,6 +27,14 @@ class NitroTextView(ctx: Context) : ReactViewGroup(ctx){ setHorizontallyScrolling(false) } + var nitroTextDelegate: NitroTextViewDelegate? = null + set(value) { + field = value + scheduleTextLayoutDispatch() + } + + private var pendingLayoutDispatch = false + init { // Fill the container; borders/radius are applied to this container by RN. addView( @@ -35,6 +52,7 @@ class NitroTextView(ctx: Context) : ReactViewGroup(ctx){ val childRight = measuredWidth - paddingRight val childBottom = measuredHeight - paddingBottom textView.layout(childLeft, childTop, childRight, childBottom) + scheduleTextLayoutDispatch() } // Block adding of any other children to this container (we only have a single TextView) @@ -45,4 +63,71 @@ class NitroTextView(ctx: Context) : ReactViewGroup(ctx){ override fun addView(child: View?, index: Int, params: LayoutParams?) { if (child === textView) super.addView(child, index, params) } + + private fun scheduleTextLayoutDispatch() { + val delegate = nitroTextDelegate ?: return + if (pendingLayoutDispatch) return + pendingLayoutDispatch = true + textView.post { + pendingLayoutDispatch = false + val event = buildTextLayoutEvent() + if (event != null) { + delegate.onNitroTextLayout(event) + } else if (nitroTextDelegate != null) { + scheduleTextLayoutDispatch() + } + } + } + + private fun buildTextLayoutEvent(): TextLayoutEvent? { + val layout = textView.layout ?: return null + val content = textView.text ?: return TextLayoutEvent(emptyArray()) + if (content.isEmpty()) return TextLayoutEvent(emptyArray()) + + val density = resources.displayMetrics.density + + val paintCopy = TextPaint(textView.paint).apply { textSize *= 100f } + val capBounds = Rect() + paintCopy.getTextBounds("T", 0, 1, capBounds) + val capHeight = capBounds.height() / 100f / density + + val xBounds = Rect() + paintCopy.getTextBounds("x", 0, 1, xBounds) + val xHeight = xBounds.height() / 100f / density + + val totalLines = layout.lineCount + val leftOffset = textView.left + textView.totalPaddingLeft + val topOffset = textView.top + textView.totalPaddingTop + val lines = Array(totalLines) { lineIndex -> + val start = layout.getLineStart(lineIndex) + val end = layout.getLineEnd(lineIndex) + val lineText = content.subSequence(start, end).toString() + + val endsWithNewline = end > start && content[end - 1] == '\n' + val widthPx = if (endsWithNewline) layout.getLineMax(lineIndex) else layout.getLineWidth(lineIndex) + val lineLeft = layout.getLineLeft(lineIndex) + val lineTop = layout.getLineTop(lineIndex) + val lineBottom = layout.getLineBottom(lineIndex) + + val x = (leftOffset + lineLeft) / density + val y = (topOffset + lineTop) / density + val height = (lineBottom - lineTop) / density + val descender = layout.getLineDescent(lineIndex) / density + val ascender = -layout.getLineAscent(lineIndex) / density + + TextLayout( + text = lineText, + x = x.toDouble(), + y = y.toDouble(), + width = (widthPx / density).toDouble(), + height = height.toDouble(), + descender = descender.toDouble(), + capHeight = capHeight.toDouble(), + ascender = ascender.toDouble(), + xHeight = xHeight.toDouble() + ) + } + + return TextLayoutEvent(lines) + } } diff --git a/cpp/NitroTextShadowNode.cpp b/cpp/NitroTextShadowNode.cpp index 8b9e795..75770ab 100644 --- a/cpp/NitroTextShadowNode.cpp +++ b/cpp/NitroTextShadowNode.cpp @@ -16,6 +16,14 @@ #include #endif +#if defined(REACT_NATIVE_VERSION_MAJOR) +#define RN_VERSION_AT_LEAST(major, minor) \ + ((REACT_NATIVE_VERSION_MAJOR > (major)) || \ + (REACT_NATIVE_VERSION_MAJOR == (major) && REACT_NATIVE_VERSION_MINOR >= (minor))) +#else +#define RN_VERSION_AT_LEAST(major, minor) 0 +#endif + namespace margelo::nitro::nitrotext::views { react::ShadowNodeTraits NitroTextShadowNode::BaseTraits() @@ -366,14 +374,10 @@ react::Size NitroTextShadowNode::measureContent( // but leaving the scale here avoids over-adjusting and potential divergences. if (props.minimumFontScale.value.has_value()) { -#if defined(REACT_NATIVE_VERSION_MAJOR) -#if (REACT_NATIVE_VERSION_MAJOR > 0) || \ - (REACT_NATIVE_VERSION_MAJOR == 0 && REACT_NATIVE_VERSION_MINOR >= 81) - paragraphAttributes.minimumFontScale = - props.minimumFontScale.value.value(); +#if RN_VERSION_AT_LEAST(0, 81) + paragraphAttributes.minimumFontScale = props.minimumFontScale.value.value(); #else // React Native < 0.81 does not expose paragraphAttributes.minimumFontScale yet. -#endif #endif } @@ -411,8 +415,10 @@ react::Size NitroTextShadowNode::measureContent( // Measure using given constraints (Yoga already accounts for padding/border). react::TextLayoutContext textLayoutContext{ .pointScaleFactor = layoutContext.pointScaleFactor, - // TODO: investigate why surfaceId is not working for react-native <= 0.79 - .surfaceId = this->getSurfaceId(), +#if RN_VERSION_AT_LEAST(0, 81) + // Older React Native versions didn't surface `surfaceId` on TextLayoutContext. + .surfaceId = this->getSurfaceId(), +#endif }; const auto measurement = textLayoutManager_->measure( diff --git a/example/App.tsx b/example/App.tsx index fe84fa4..62e92bd 100644 --- a/example/App.tsx +++ b/example/App.tsx @@ -11,7 +11,7 @@ export default function App() { }; const handleTextLayout = (event: TextLayoutEvent) => { - // console.log('lines', lines); + console.log('lines', event.lines); // console.log('width', event); // console.log('height', height); // setLayoutInfo(`Lines: ${lines.length}`); @@ -286,7 +286,6 @@ const styles = StyleSheet.create({ lineHeight: 24, borderWidth: 1, borderColor: 'blue', - width: '100%', }, sectionTitle: { fontSize: 24, From 7b138c111a7e36145e059f8a732d6a9420e6745c Mon Sep 17 00:00:00 2001 From: Patrick Kabwe Date: Mon, 29 Sep 2025 08:41:26 +0200 Subject: [PATCH 03/12] feat: implement fragment background color support in NitroText --- .../java/com/nitrotext/HybridNitroText.kt | 2 +- .../main/java/com/nitrotext/NitroTextImpl.kt | 27 ++++++++++++++----- .../main/java/com/nitrotext/NitroTextView.kt | 2 +- example/App.tsx | 8 +++--- 4 files changed, 26 insertions(+), 13 deletions(-) diff --git a/android/src/main/java/com/nitrotext/HybridNitroText.kt b/android/src/main/java/com/nitrotext/HybridNitroText.kt index 74cd3b5..206e520 100755 --- a/android/src/main/java/com/nitrotext/HybridNitroText.kt +++ b/android/src/main/java/com/nitrotext/HybridNitroText.kt @@ -127,7 +127,7 @@ class HybridNitroText(val context: ThemedReactContext) : HybridNitroTextSpec(), override var fragmentBackgroundColor: String? get() = null set(value) { - // TODO: Implement fragmentBackgroundColor + impl.setFragmentBackgroundColor(value) } override var fontStyle: FontStyle? diff --git a/android/src/main/java/com/nitrotext/NitroTextImpl.kt b/android/src/main/java/com/nitrotext/NitroTextImpl.kt index 02efe2d..79a0b3c 100644 --- a/android/src/main/java/com/nitrotext/NitroTextImpl.kt +++ b/android/src/main/java/com/nitrotext/NitroTextImpl.kt @@ -1,5 +1,6 @@ package com.nitrotext +import android.graphics.Color import android.graphics.Typeface import android.text.Spannable import android.text.SpannableStringBuilder @@ -43,6 +44,8 @@ class NitroTextImpl(private val view: AppCompatTextView) { private var textDecorationColor: String? = null private var textDecorationStyle: TextDecorationStyle? = null + private var fragmentBackgroundColor: String? = null + fun commit() { // Reset typography to avoid stale values from recycled views view.setLineSpacing(0f, 1f) @@ -86,6 +89,10 @@ class NitroTextImpl(private val view: AppCompatTextView) { fun setTextDecorationColor(value: String?) { textDecorationColor = value } fun setTextDecorationStyle(value: TextDecorationStyle?) { textDecorationStyle = value } + fun setFragmentBackgroundColor(value: String?) { + fragmentBackgroundColor = value + } + // Apply helpers private fun applySelectable() { selectable?.let { view.setTextIsSelectable(it) } @@ -127,11 +134,12 @@ class NitroTextImpl(private val view: AppCompatTextView) { } private fun applyAlignment() { - when (textAlign) { - TextAlign.LEFT -> view.gravity = Gravity.START or Gravity.CENTER_VERTICAL - TextAlign.RIGHT -> view.gravity = Gravity.END or Gravity.CENTER_VERTICAL - TextAlign.CENTER -> view.gravity = Gravity.CENTER - TextAlign.JUSTIFY, TextAlign.AUTO, null -> Unit + val verticalCenter = Gravity.CENTER_VERTICAL + view.gravity = when (textAlign) { + TextAlign.LEFT -> Gravity.START or verticalCenter + TextAlign.RIGHT -> Gravity.END or verticalCenter + TextAlign.CENTER -> Gravity.CENTER_HORIZONTAL or verticalCenter + TextAlign.JUSTIFY, TextAlign.AUTO, null -> Gravity.START or verticalCenter } } @@ -152,7 +160,7 @@ class NitroTextImpl(private val view: AppCompatTextView) { view.text = "" } - fontColor?.let { parseColorSafe(it)?.let(view::setTextColor) } + view.setTextColor(resolvedFontColor()) fontSize?.let { if (allowFontScaling) { view.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, it.toFloat()) @@ -206,7 +214,7 @@ class NitroTextImpl(private val view: AppCompatTextView) { view.text = builder // Apply default text color for runs without explicit color - fontColor?.let { parseColorSafe(it)?.let(view::setTextColor) } + view.setTextColor(resolvedFontColor()) } private fun applyDecorationSpans(builder: SpannableStringBuilder, start: Int, end: Int, line: TextDecorationLine?) { @@ -252,6 +260,11 @@ class NitroTextImpl(private val view: AppCompatTextView) { str.toColorInt() } catch (_: Throwable) { null } + private fun resolvedFontColor(): Int { + val parsed = fontColor?.let { parseColorSafe(it) } + return parsed ?: Color.BLACK + } + private fun resolveLineHeight(value: Double?): Float? { val raw = value ?: return null val px = if (allowFontScaling) { diff --git a/android/src/main/java/com/nitrotext/NitroTextView.kt b/android/src/main/java/com/nitrotext/NitroTextView.kt index 063540a..6ae7e14 100644 --- a/android/src/main/java/com/nitrotext/NitroTextView.kt +++ b/android/src/main/java/com/nitrotext/NitroTextView.kt @@ -20,7 +20,7 @@ interface NitroTextViewDelegate { @SuppressLint("ViewConstructor") class NitroTextView(ctx: Context) : ReactViewGroup(ctx) { val textView = AppCompatTextView(ctx).apply { - includeFontPadding = false + includeFontPadding = true minWidth = 0; minHeight = 0 breakStrategy = LineBreaker.BREAK_STRATEGY_HIGH_QUALITY hyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NORMAL diff --git a/example/App.tsx b/example/App.tsx index 62e92bd..283506d 100644 --- a/example/App.tsx +++ b/example/App.tsx @@ -159,14 +159,14 @@ export default function App() { NitroText can seamlessly integrate with React Native's Text component: {'\n\n'} - This is a React Native Text component{' '} - with nested NitroText{' '} + This is a React Native Text component + with nested NitroText inside it. {'\n\n'}And vice versa - NitroText can contain:{'\n'} - Regular text with{' '} - RN Text nested inside{' '} + Regular text with + {' '}RN Text nested inside{' '} NitroText. From e246f9e5e7594eb389e24813c18378e4e54715be Mon Sep 17 00:00:00 2001 From: Patrick Kabwe Date: Wed, 1 Oct 2025 22:17:31 +0200 Subject: [PATCH 04/12] feat(android): html support --- android/CMakeLists.txt | 2 + android/build.gradle | 4 +- .../com/nitrotext/HtmlLinkMovementMethod.kt | 47 ++ .../java/com/nitrotext/HybridNitroText.kt | 12 + .../java/com/nitrotext/NitroLineHeightSpan.kt | 29 -- .../main/java/com/nitrotext/NitroTextImpl.kt | 74 ++- .../nitrotext/renderers/NitroHtmlRenderer.kt | 467 ++++++++++++++++++ .../renderers/NitroRendererInterface.kt | 9 + .../com/nitrotext/spans/BulletListSpan.kt | 48 ++ .../com/nitrotext/spans/LetterSpacingSpan.kt | 14 + .../nitrotext/spans/NitroLineHeightSpan.kt | 29 ++ .../com/nitrotext/spans/NumberedListSpan.kt | 49 ++ .../com/nitrotext/spans/UrlSpanNoUnderline.kt | 11 + .../com/nitrotext/spans/VerticalMarginSpan.kt | 31 ++ cpp/NitroHtmlUtils.cpp | 272 ++++++++++ cpp/NitroHtmlUtils.hpp | 19 + cpp/NitroTextShadowNode.cpp | 37 +- example/App.tsx | 58 ++- ios/HybridNitroText.swift | 13 + .../android/c++/JHybridNitroTextSpec.cpp | 47 ++ .../android/c++/JHybridNitroTextSpec.hpp | 4 + .../generated/android/c++/JNitroRenderer.hpp | 59 +++ .../generated/android/c++/JRichTextStyle.hpp | 130 +++++ .../android/c++/JRichTextStyleRule.hpp | 72 +++ .../views/JHybridNitroTextStateUpdater.cpp | 8 + .../nitro/nitrotext/HybridNitroTextSpec.kt | 12 + .../margelo/nitro/nitrotext/NitroRenderer.kt | 21 + .../margelo/nitro/nitrotext/RichTextStyle.kt | 77 +++ .../nitro/nitrotext/RichTextStyleRule.kt | 32 ++ .../nitrotext/views/HybridNitroTextManager.kt | 2 +- .../ios/NitroText-Swift-Cxx-Bridge.hpp | 50 ++ .../ios/NitroText-Swift-Cxx-Umbrella.hpp | 9 + .../ios/c++/HybridNitroTextSpecSwift.hpp | 23 + .../ios/c++/views/HybridNitroTextComponent.mm | 10 + .../ios/swift/HybridNitroTextSpec.swift | 2 + .../ios/swift/HybridNitroTextSpec_cxx.swift | 47 ++ .../generated/ios/swift/NitroRenderer.swift | 40 ++ .../generated/ios/swift/RichTextStyle.swift | 443 +++++++++++++++++ .../ios/swift/RichTextStyleRule.swift | 46 ++ .../shared/c++/HybridNitroTextSpec.cpp | 4 + .../shared/c++/HybridNitroTextSpec.hpp | 10 + .../generated/shared/c++/NitroRenderer.hpp | 76 +++ .../generated/shared/c++/RichTextStyle.hpp | 149 ++++++ .../shared/c++/RichTextStyleRule.hpp | 73 +++ .../c++/views/HybridNitroTextComponent.cpp | 24 + .../c++/views/HybridNitroTextComponent.hpp | 4 + .../shared/json/NitroTextConfig.json | 2 + src/nitro-text.tsx | 48 +- src/specs/nitro-text.nitro.ts | 12 + src/types.ts | 27 + src/utils.ts | 28 +- 51 files changed, 2763 insertions(+), 53 deletions(-) create mode 100644 android/src/main/java/com/nitrotext/HtmlLinkMovementMethod.kt delete mode 100644 android/src/main/java/com/nitrotext/NitroLineHeightSpan.kt create mode 100644 android/src/main/java/com/nitrotext/renderers/NitroHtmlRenderer.kt create mode 100644 android/src/main/java/com/nitrotext/renderers/NitroRendererInterface.kt create mode 100644 android/src/main/java/com/nitrotext/spans/BulletListSpan.kt create mode 100644 android/src/main/java/com/nitrotext/spans/LetterSpacingSpan.kt create mode 100644 android/src/main/java/com/nitrotext/spans/NitroLineHeightSpan.kt create mode 100644 android/src/main/java/com/nitrotext/spans/NumberedListSpan.kt create mode 100644 android/src/main/java/com/nitrotext/spans/UrlSpanNoUnderline.kt create mode 100644 android/src/main/java/com/nitrotext/spans/VerticalMarginSpan.kt create mode 100644 cpp/NitroHtmlUtils.cpp create mode 100644 cpp/NitroHtmlUtils.hpp create mode 100644 nitrogen/generated/android/c++/JNitroRenderer.hpp create mode 100644 nitrogen/generated/android/c++/JRichTextStyle.hpp create mode 100644 nitrogen/generated/android/c++/JRichTextStyleRule.hpp create mode 100644 nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/NitroRenderer.kt create mode 100644 nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/RichTextStyle.kt create mode 100644 nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/RichTextStyleRule.kt create mode 100644 nitrogen/generated/ios/swift/NitroRenderer.swift create mode 100644 nitrogen/generated/ios/swift/RichTextStyle.swift create mode 100644 nitrogen/generated/ios/swift/RichTextStyleRule.swift create mode 100644 nitrogen/generated/shared/c++/NitroRenderer.hpp create mode 100644 nitrogen/generated/shared/c++/RichTextStyle.hpp create mode 100644 nitrogen/generated/shared/c++/RichTextStyleRule.hpp diff --git a/android/CMakeLists.txt b/android/CMakeLists.txt index 740acc8..fd63c0d 100644 --- a/android/CMakeLists.txt +++ b/android/CMakeLists.txt @@ -25,6 +25,8 @@ include_directories( # Add custom ShadowNode implementation target_sources( ${PACKAGE_NAME} PRIVATE + ../cpp/NitroHtmlUtils.cpp + ../cpp/NitroHtmlUtils.hpp ../cpp/NitroTextShadowNode.cpp ../cpp/NitroTextShadowNode.hpp ../cpp/NitroTextComponentDescriptor.cpp diff --git a/android/build.gradle b/android/build.gradle index a1abdce..e6affaa 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -137,6 +137,8 @@ dependencies { // Add a dependency on NitroModules implementation project(":react-native-nitro-modules") + implementation "org.jsoup:jsoup:1.18.1" + } if (isNewArchitectureEnabled()) { @@ -145,4 +147,4 @@ if (isNewArchitectureEnabled()) { libraryName = "NitroText" codegenJavaPackageName = "com.nitrotext" } -} \ No newline at end of file +} diff --git a/android/src/main/java/com/nitrotext/HtmlLinkMovementMethod.kt b/android/src/main/java/com/nitrotext/HtmlLinkMovementMethod.kt new file mode 100644 index 0000000..d0f1bf2 --- /dev/null +++ b/android/src/main/java/com/nitrotext/HtmlLinkMovementMethod.kt @@ -0,0 +1,47 @@ +package com.nitrotext + +import android.text.Selection +import android.text.Spannable +import android.text.method.MovementMethod +import android.text.style.ClickableSpan +import android.view.MotionEvent +import android.widget.TextView + +internal object HtmlLinkMovementMethod : MovementMethod { + override fun initialize(widget: TextView?, text: Spannable?) = Unit + override fun onKeyDown(widget: TextView?, text: Spannable?, keyCode: Int, event: android.view.KeyEvent?) = false + override fun onKeyUp(widget: TextView?, text: Spannable?, keyCode: Int, event: android.view.KeyEvent?) = false + override fun onKeyOther(widget: TextView?, text: Spannable?, event: android.view.KeyEvent?) = false + override fun onTrackballEvent(widget: TextView?, text: Spannable?, event: MotionEvent?) = false + override fun onTakeFocus(widget: TextView?, text: Spannable?, direction: Int) = Unit + override fun canSelectArbitrarily() = false + override fun onGenericMotionEvent(widget: TextView?, text: Spannable?, event: MotionEvent?) = false + + override fun onTouchEvent(widget: TextView, buffer: Spannable, event: MotionEvent): Boolean { + val action = event.action + + if (action != MotionEvent.ACTION_UP && action != MotionEvent.ACTION_DOWN) { + return false + } + + val layout = widget.layout ?: return false + val x = event.x.toInt() - widget.totalPaddingLeft + widget.scrollX + val y = event.y.toInt() - widget.totalPaddingTop + widget.scrollY + + val line = layout.getLineForVertical(y) + val offset = layout.getOffsetForHorizontal(line, x.toFloat()) + + val links = buffer.getSpans(offset, offset, ClickableSpan::class.java) + if (links.isNotEmpty()) { + val link = links[0] + if (action == MotionEvent.ACTION_UP) { + link.onClick(widget) + } else { + Selection.setSelection(buffer, buffer.getSpanStart(link), buffer.getSpanEnd(link)) + } + return true + } + + return false + } +} diff --git a/android/src/main/java/com/nitrotext/HybridNitroText.kt b/android/src/main/java/com/nitrotext/HybridNitroText.kt index 206e520..62189f3 100755 --- a/android/src/main/java/com/nitrotext/HybridNitroText.kt +++ b/android/src/main/java/com/nitrotext/HybridNitroText.kt @@ -22,6 +22,18 @@ class HybridNitroText(val context: ThemedReactContext) : HybridNitroTextSpec(), impl.setFragments(value) } + override var renderer: NitroRenderer? + get() = null + set(value) { + impl.setRenderer(value) + } + + override var richTextStyleRules: Array? + get() = null + set(value) { + impl.setRichTextStyleRules(value) + } + override var selectable: Boolean? get() = null set(value) { diff --git a/android/src/main/java/com/nitrotext/NitroLineHeightSpan.kt b/android/src/main/java/com/nitrotext/NitroLineHeightSpan.kt deleted file mode 100644 index cc1487a..0000000 --- a/android/src/main/java/com/nitrotext/NitroLineHeightSpan.kt +++ /dev/null @@ -1,29 +0,0 @@ -package com.nitrotext - -import android.graphics.Paint -import android.text.style.LineHeightSpan -import kotlin.math.ceil -import kotlin.math.floor - -/** - * Matches React Native's CustomLineHeightSpan so lineHeight behaves the same as . - */ -class NitroLineHeightSpan(heightPx: Float) : LineHeightSpan { - private val lineHeight: Int = ceil(heightPx.toDouble()).toInt() - - override fun chooseHeight( - text: CharSequence, - start: Int, - end: Int, - spanstartv: Int, - v: Int, - fm: Paint.FontMetricsInt - ) { - val leading = lineHeight - ((-fm.ascent) + fm.descent) - val halfLeading = leading / 2f - fm.ascent -= ceil(halfLeading.toDouble()).toInt() - fm.descent += floor(halfLeading.toDouble()).toInt() - if (start == 0) fm.top = fm.ascent - if (end == text.length) fm.bottom = fm.descent - } -} diff --git a/android/src/main/java/com/nitrotext/NitroTextImpl.kt b/android/src/main/java/com/nitrotext/NitroTextImpl.kt index 79a0b3c..ef85682 100644 --- a/android/src/main/java/com/nitrotext/NitroTextImpl.kt +++ b/android/src/main/java/com/nitrotext/NitroTextImpl.kt @@ -7,21 +7,28 @@ import android.text.SpannableStringBuilder import android.text.TextUtils import android.text.style.AbsoluteSizeSpan import android.text.style.BackgroundColorSpan +import android.text.style.ClickableSpan import android.text.style.ForegroundColorSpan import android.text.style.StrikethroughSpan import android.text.style.StyleSpan import android.text.style.TypefaceSpan import android.text.style.UnderlineSpan import android.view.Gravity +import android.text.method.ArrowKeyMovementMethod +import android.text.method.LinkMovementMethod import androidx.appcompat.widget.AppCompatTextView import com.facebook.react.uimanager.PixelUtil import com.margelo.nitro.nitrotext.* import androidx.core.graphics.toColorInt +import com.nitrotext.renderers.NitroHtmlRenderer +import com.nitrotext.spans.NitroLineHeightSpan class NitroTextImpl(private val view: AppCompatTextView) { // Stored props private var fragments: Array? = null private var text: String? = null + private var renderer: NitroRenderer = NitroRenderer.PLAINTEXT + private var richTextStyleRules: Array? = null private var selectable: Boolean? = null private var selectionColor: String? = null @@ -56,7 +63,11 @@ class NitroTextImpl(private val view: AppCompatTextView) { applyAlignment() val frags = fragments - if (!frags.isNullOrEmpty()) { + val content = text + + if (renderer == NitroRenderer.HTML && !content.isNullOrEmpty()) { + applyHtml(content) + } else if (!frags.isNullOrEmpty()) { applyFragments(frags) } else { applySimpleText() @@ -68,6 +79,8 @@ class NitroTextImpl(private val view: AppCompatTextView) { // Setters fun setFragments(value: Array?) { fragments = value } fun setText(value: String?) { text = value } + fun setRenderer(value: NitroRenderer?) { renderer = value ?: NitroRenderer.PLAINTEXT } + fun setRichTextStyleRules(value: Array?) { richTextStyleRules = value } fun setSelectable(value: Boolean?) { selectable = value } fun setSelectionColor(value: String?) { selectionColor = value } @@ -212,7 +225,7 @@ class NitroTextImpl(private val view: AppCompatTextView) { } containerLineHeightPx?.let { applyLineHeightSpan(builder, 0, builder.length, it) } view.text = builder - + // Apply default text color for runs without explicit color view.setTextColor(resolvedFontColor()) } @@ -265,6 +278,59 @@ class NitroTextImpl(private val view: AppCompatTextView) { return parsed ?: Color.BLACK } + private fun baseRichTextStyle(): RichTextStyle { + return RichTextStyle( + fontColor = fontColor, + fragmentBackgroundColor = fragmentBackgroundColor, + fontSize = fontSize, + fontWeight = fontWeight, + fontStyle = fontStyle, + fontFamily = fontFamily, + lineHeight = lineHeight, + letterSpacing = letterSpacing, + textAlign = textAlign, + textTransform = textTransform, + textDecorationLine = textDecorationLine, + textDecorationColor = textDecorationColor, + textDecorationStyle = textDecorationStyle, + marginTop = null, + marginBottom = null, + marginLeft = null, + marginRight = null, + ) + } + + private fun applyHtml(html: String) { + val renderer = NitroHtmlRenderer( + context = view.context, + defaultTextSizePx = view.textSize, + allowFontScaling = allowFontScaling, + maxFontSizeMultiplier = maxFontSizeMultiplier, + ) + val spannable = renderer.render(html, baseRichTextStyle(), richTextStyleRules) + trimTrailingNewlines(spannable) + view.text = spannable + val hasLinks = spannable.getSpans(0, spannable.length, ClickableSpan::class.java).isNotEmpty() + view.linksClickable = hasLinks + val isSelectable = selectable == true + view.movementMethod = when { + hasLinks && isSelectable -> LinkMovementMethod.getInstance() + hasLinks -> HtmlLinkMovementMethod + isSelectable -> ArrowKeyMovementMethod.getInstance() + else -> null + } + view.setTextIsSelectable(isSelectable) + view.setTextColor(resolvedFontColor()) + } + + private fun trimTrailingNewlines(builder: SpannableStringBuilder) { + var length = builder.length + while (length > 0 && builder[length - 1] == '\n') { + builder.delete(length - 1, length) + length = builder.length + } + } + private fun resolveLineHeight(value: Double?): Float? { val raw = value ?: return null val px = if (allowFontScaling) { @@ -294,8 +360,4 @@ class NitroTextImpl(private val view: AppCompatTextView) { } builder.setSpan(NitroLineHeightSpan(lineHeightPx), start, end, flags) } - - companion object { - private const val TAG = "NitroTextImpl" - } } diff --git a/android/src/main/java/com/nitrotext/renderers/NitroHtmlRenderer.kt b/android/src/main/java/com/nitrotext/renderers/NitroHtmlRenderer.kt new file mode 100644 index 0000000..f680bc7 --- /dev/null +++ b/android/src/main/java/com/nitrotext/renderers/NitroHtmlRenderer.kt @@ -0,0 +1,467 @@ +package com.nitrotext.renderers + +import android.content.Context +import android.graphics.Typeface +import android.text.SpannableStringBuilder +import android.text.Spanned +import android.text.style.AbsoluteSizeSpan +import android.text.style.BackgroundColorSpan +import android.text.style.ForegroundColorSpan +import android.text.style.StrikethroughSpan +import android.text.style.StyleSpan +import android.text.style.TypefaceSpan +import android.text.style.UnderlineSpan +import com.facebook.react.uimanager.PixelUtil +import com.margelo.nitro.nitrotext.FontStyle +import com.margelo.nitro.nitrotext.FontWeight +import com.margelo.nitro.nitrotext.RichTextStyle +import com.margelo.nitro.nitrotext.RichTextStyleRule +import com.margelo.nitro.nitrotext.TextAlign +import com.margelo.nitro.nitrotext.TextDecorationLine +import com.margelo.nitro.nitrotext.TextDecorationStyle +import com.margelo.nitro.nitrotext.TextTransform +import org.jsoup.Jsoup +import org.jsoup.nodes.Element +import org.jsoup.nodes.Node +import org.jsoup.nodes.TextNode +import java.util.Locale +import androidx.core.graphics.toColorInt +import com.nitrotext.spans.BulletListSpan +import com.nitrotext.spans.LetterSpacingSpan +import com.nitrotext.spans.NitroLineHeightSpan +import com.nitrotext.spans.NumberedListSpan +import com.nitrotext.spans.UrlSpanNoUnderline +import com.nitrotext.spans.VerticalMarginSpan + +/** + * Converts HTML markup to a SpannableStringBuilder while applying Nitro rich text styles. + */ +class NitroHtmlRenderer( + private val context: Context, + private val defaultTextSizePx: Float, + private val allowFontScaling: Boolean, + private val maxFontSizeMultiplier: Double? +) : NitroRendererInterface { + + private val listLeadingMarginPx = PixelUtil.toPixelFromDIP(24f).toInt() + private val listGapPx = PixelUtil.toPixelFromDIP(8f).toInt() + private val bulletRadiusPx = PixelUtil.toPixelFromDIP(3f) + + override fun render( + input: String, + baseStyle: RichTextStyle?, + rules: Array? + ): SpannableStringBuilder { + val builder = SpannableStringBuilder() + val document = Jsoup.parse(input) + val ruleMap = buildRuleMap(rules) + + val stateStack = ArrayDeque() + stateStack.addLast(StyleState(baseStyle ?: EMPTY_STYLE, preserveWhitespace = false)) + + val blockStack = ArrayDeque() + val blockRanges = mutableListOf() + val listStack = ArrayDeque() + + traverse(document.body(), builder, stateStack, blockStack, blockRanges, listStack, ruleMap) + + applyBlockMargins(builder, blockRanges) + return builder + } + + private fun traverse( + node: Node, + builder: SpannableStringBuilder, + stateStack: ArrayDeque, + blockStack: ArrayDeque, + blockRanges: MutableList, + listStack: ArrayDeque, + ruleMap: Map + ) { + when (node) { + is TextNode -> appendText(node, builder, stateStack.last()) + is Element -> handleElement(node, builder, stateStack, blockStack, blockRanges, listStack, ruleMap) + } + } + + private fun appendText(textNode: TextNode, builder: SpannableStringBuilder, state: StyleState) { + val raw = if (state.preserveWhitespace) textNode.wholeText else textNode.text() + if (raw.isEmpty()) return + + val content = applyTextTransform(raw, state.style.textTransform) + if (content.isEmpty()) return + + val start = builder.length + builder.append(content) + val end = builder.length + + val style = state.style + + style.fontColor?.let { parseColorSafe(it)?.let { c -> + builder.setSpan(ForegroundColorSpan(c), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + } } + + style.fragmentBackgroundColor?.let { parseColorSafe(it)?.let { c -> + builder.setSpan(BackgroundColorSpan(c), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + } } + + style.fontSize?.let { size -> + builder.setSpan(AbsoluteSizeSpan(size.toInt(), true), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + } + + when (style.fontWeight) { + FontWeight.BOLD, FontWeight.SEMIBOLD, FontWeight.HEAVY, FontWeight.BLACK -> + builder.setSpan(StyleSpan(Typeface.BOLD), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + FontWeight.ULTRALIGHT, + FontWeight.THIN, + FontWeight.LIGHT, + FontWeight.MEDIUM, + FontWeight.REGULAR, + FontWeight.CONDENSED, + FontWeight.CONDENSEDBOLD, + FontWeight.NORMAL, + null -> Unit + } + + when (style.fontStyle) { + FontStyle.ITALIC, FontStyle.OBLIQUE -> builder.setSpan(StyleSpan(Typeface.ITALIC), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + FontStyle.NORMAL, null -> Unit + } + + style.fontFamily?.let { + builder.setSpan(TypefaceSpan(it), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + } + + when (style.textDecorationLine) { + TextDecorationLine.UNDERLINE -> builder.setSpan(UnderlineSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + TextDecorationLine.LINE_THROUGH -> builder.setSpan(StrikethroughSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + TextDecorationLine.UNDERLINE_LINE_THROUGH -> { + builder.setSpan(UnderlineSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + builder.setSpan(StrikethroughSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + } + TextDecorationLine.NONE, null -> Unit + } + + style.lineHeight?.let { value -> + val px = resolveLineHeight(value) + if (px > 0f) builder.setSpan(NitroLineHeightSpan(px), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + } + + style.letterSpacing?.let { spacing -> + val em = resolveLetterSpacing(spacing) + if (em != 0f) builder.setSpan(LetterSpacingSpan(em), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + } + + state.linkHref?.let { url -> + builder.setSpan(UrlSpanNoUnderline(url), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + } + } + + private fun handleElement( + element: Element, + builder: SpannableStringBuilder, + stateStack: ArrayDeque, + blockStack: ArrayDeque, + blockRanges: MutableList, + listStack: ArrayDeque, + ruleMap: Map + ) { + val tag = element.normalName().lowercase(Locale.US) + + if (tag == "br") { + builder.append('\n') + return + } + + val current = stateStack.last() + val ruleStyle = ruleMap[tag] + val tagStyle = defaultStyleForTag(tag) + val merged = mergeStyles(current.style, tagStyle) + val finalStyle = mergeStyles(merged, ruleStyle) + val linkHref = when (tag) { + "a" -> element.attr("href").takeIf { it.isNotEmpty() } ?: current.linkHref + else -> current.linkHref + } + + val preserveWhitespace = current.preserveWhitespace || tag == "pre" + + val isListContainer = tag == "ul" || tag == "ol" + if (isListContainer) { + listStack.addLast(ListContext(if (tag == "ol") ListType.ORDERED else ListType.UNORDERED)) + } + + val listContextForItem = if (tag == "li") listStack.lastOrNull() else null + val listMarkerIndex = if (listContextForItem?.type == ListType.ORDERED) { + listContextForItem.nextIndex() + } else null + + val isBlock = tag in BLOCK_TAGS || tag == "li" + val blockStartIndex = if (isBlock) beginBlock(builder) else -1 + val blockContext = if (isBlock) { + val top = finalStyle.marginTop?.let { toPx(it) } ?: 0f + val bottom = finalStyle.marginBottom?.let { toPx(it) } ?: 0f + BlockContext(blockStartIndex, top, bottom) + } else null + + blockContext?.let { blockStack.addLast(it) } + + val childState = StyleState(finalStyle, linkHref, preserveWhitespace) + stateStack.addLast(childState) + + val children = element.childNodes() + for (child in children) { + traverse(child, builder, stateStack, blockStack, blockRanges, listStack, ruleMap) + } + + stateStack.removeLast() + + var blockEnd = -1 + if (isBlock) { + val ctx = blockStack.removeLast() + blockEnd = endBlock(builder) + if (blockEnd > ctx.start) { + blockRanges.add(BlockRange(ctx.start, blockEnd, ctx.topMarginPx, ctx.bottomMarginPx)) + } + } + + if (tag == "li" && blockStartIndex >= 0 && blockEnd > blockStartIndex) { + applyListSpan( + builder = builder, + start = blockStartIndex, + end = blockEnd, + listType = listContextForItem?.type, + listIndex = listMarkerIndex, + depth = listStack.size + ) + } + + if (isListContainer && listStack.isNotEmpty()) { + listStack.removeLast() + } + } + + private fun beginBlock(builder: SpannableStringBuilder): Int { + ensureTrailingNewline(builder) + return builder.length + } + + private fun endBlock(builder: SpannableStringBuilder): Int { + val end = builder.length + ensureTrailingNewline(builder) + return end + } + + private fun ensureTrailingNewline(builder: SpannableStringBuilder) { + val length = builder.length + if (length == 0) return + var index = length - 1 + while (index >= 0 && builder[index] == ' ') index-- + if (index >= 0 && builder[index] != '\n') { + builder.append('\n') + } + } + + private fun applyBlockMargins(builder: SpannableStringBuilder, ranges: List) { + for (range in ranges) { + if (range.end <= range.start) continue + if (range.topMarginPx <= 0f && range.bottomMarginPx <= 0f) continue + builder.setSpan( + VerticalMarginSpan(range.topMarginPx, range.bottomMarginPx), + range.start, + range.end, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE + ) + } + } + + private fun applyListSpan( + builder: SpannableStringBuilder, + start: Int, + end: Int, + listType: ListType?, + listIndex: Int?, + depth: Int + ) { + if (listType == null || start >= end) return + val effectiveDepth = depth.coerceAtLeast(1) + val leadingMargin = listLeadingMarginPx * effectiveDepth + val span = when (listType) { + ListType.UNORDERED -> BulletListSpan(leadingMargin, listGapPx, bulletRadiusPx) + ListType.ORDERED -> NumberedListSpan(listIndex ?: 0, leadingMargin, listGapPx) + } + builder.setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + } + + private fun mergeStyles(base: RichTextStyle, override: RichTextStyle?): RichTextStyle { + if (override == null) return base + return createStyle( + fontColor = override.fontColor ?: base.fontColor, + fragmentBackgroundColor = override.fragmentBackgroundColor ?: base.fragmentBackgroundColor, + fontSize = override.fontSize ?: base.fontSize, + fontWeight = override.fontWeight ?: base.fontWeight, + fontStyle = override.fontStyle ?: base.fontStyle, + fontFamily = override.fontFamily ?: base.fontFamily, + lineHeight = override.lineHeight ?: base.lineHeight, + letterSpacing = override.letterSpacing ?: base.letterSpacing, + textAlign = override.textAlign ?: base.textAlign, + textTransform = override.textTransform ?: base.textTransform, + textDecorationLine = mergeDecorations(base.textDecorationLine, override.textDecorationLine), + textDecorationColor = override.textDecorationColor ?: base.textDecorationColor, + textDecorationStyle = override.textDecorationStyle ?: base.textDecorationStyle, + marginTop = override.marginTop ?: base.marginTop, + marginBottom = override.marginBottom ?: base.marginBottom, + marginLeft = override.marginLeft ?: base.marginLeft, + marginRight = override.marginRight ?: base.marginRight, + ) + } + + private fun mergeDecorations(a: TextDecorationLine?, b: TextDecorationLine?): TextDecorationLine? { + if (b == null) return a + if (a == null) return b + if (a == b) return a + val underline = a == TextDecorationLine.UNDERLINE || a == TextDecorationLine.UNDERLINE_LINE_THROUGH || + b == TextDecorationLine.UNDERLINE || b == TextDecorationLine.UNDERLINE_LINE_THROUGH + val strike = a == TextDecorationLine.LINE_THROUGH || a == TextDecorationLine.UNDERLINE_LINE_THROUGH || + b == TextDecorationLine.LINE_THROUGH || b == TextDecorationLine.UNDERLINE_LINE_THROUGH + return when { + underline && strike -> TextDecorationLine.UNDERLINE_LINE_THROUGH + underline -> TextDecorationLine.UNDERLINE + strike -> TextDecorationLine.LINE_THROUGH + else -> TextDecorationLine.NONE + } + } + + private fun defaultStyleForTag(tag: String): RichTextStyle? = when (tag) { + "strong", "b" -> createStyle(fontWeight = FontWeight.BOLD) + "em", "i" -> createStyle(fontStyle = FontStyle.ITALIC) + "u" -> createStyle(textDecorationLine = TextDecorationLine.UNDERLINE) + "s", "strike", "del" -> createStyle(textDecorationLine = TextDecorationLine.LINE_THROUGH) + "code" -> createStyle(fontFamily = "monospace") + else -> null + } + + private fun buildRuleMap(rules: Array?): Map { + if (rules == null || rules.isEmpty()) return emptyMap() + val map = HashMap(rules.size) + for (rule in rules) { + map[rule.selector.lowercase(Locale.US)] = rule.style + } + return map + } + + private fun resolveLineHeight(value: Double): Float { + return if (allowFontScaling) { + PixelUtil.toPixelFromSP(value.toFloat(), maxFontSizeMultiplier?.toFloat() ?: Float.NaN) + } else { + PixelUtil.toPixelFromDIP(value.toFloat()) + } + } + + private fun resolveLetterSpacing(value: Double): Float { + val px = if (allowFontScaling) { + PixelUtil.toPixelFromSP(value.toFloat(), maxFontSizeMultiplier?.toFloat() ?: Float.NaN) + } else { + PixelUtil.toPixelFromDIP(value.toFloat()) + } + return if (defaultTextSizePx == 0f) 0f else px / defaultTextSizePx + } + + private fun applyTextTransform(text: String, transform: TextTransform?): String { + return when (transform) { + TextTransform.UPPERCASE -> text.uppercase() + TextTransform.LOWERCASE -> text.lowercase() + TextTransform.CAPITALIZE -> text.split(' ').joinToString(" ") { part -> + if (part.isEmpty()) part else part.replaceFirstChar { ch -> ch.titlecase() } + } + TextTransform.NONE, null -> text + } + } + + private fun parseColorSafe(value: String): Int? = try { + value.toColorInt() + } catch (_: Exception) { + null + } + + private fun toPx(value: Double): Float { + return PixelUtil.toPixelFromDIP(value.toFloat()) + } + + private enum class ListType { ORDERED, UNORDERED } + + private class ListContext(val type: ListType) { + private var next = 1 + fun nextIndex(): Int { + val current = next + next += 1 + return current + } + } + + private data class StyleState( + val style: RichTextStyle, + val linkHref: String? = null, + val preserveWhitespace: Boolean = false, + ) + + private data class BlockContext( + val start: Int, + val topMarginPx: Float, + val bottomMarginPx: Float, + ) + + private data class BlockRange( + val start: Int, + val end: Int, + val topMarginPx: Float, + val bottomMarginPx: Float, + ) + + companion object { + private val BLOCK_TAGS = setOf( + "p", "div", "section", "article", "header", "footer", "aside", + "h1", "h2", "h3", "h4", "h5", "h6", "blockquote", "pre" + ) + private val EMPTY_STYLE = createStyle() + + private fun createStyle( + fontColor: String? = null, + fragmentBackgroundColor: String? = null, + fontSize: Double? = null, + fontWeight: FontWeight? = null, + fontStyle: FontStyle? = null, + fontFamily: String? = null, + lineHeight: Double? = null, + letterSpacing: Double? = null, + textAlign: TextAlign? = null, + textTransform: TextTransform? = null, + textDecorationLine: TextDecorationLine? = null, + textDecorationColor: String? = null, + textDecorationStyle: TextDecorationStyle? = null, + marginTop: Double? = null, + marginBottom: Double? = null, + marginLeft: Double? = null, + marginRight: Double? = null, + ): RichTextStyle { + return RichTextStyle( + fontColor, + fragmentBackgroundColor, + fontSize, + fontWeight, + fontStyle, + fontFamily, + lineHeight, + letterSpacing, + textAlign, + textTransform, + textDecorationLine, + textDecorationColor, + textDecorationStyle, + marginTop, + marginBottom, + marginLeft, + marginRight, + ) + } + } +} diff --git a/android/src/main/java/com/nitrotext/renderers/NitroRendererInterface.kt b/android/src/main/java/com/nitrotext/renderers/NitroRendererInterface.kt new file mode 100644 index 0000000..0e88d34 --- /dev/null +++ b/android/src/main/java/com/nitrotext/renderers/NitroRendererInterface.kt @@ -0,0 +1,9 @@ +package com.nitrotext.renderers + +import android.text.SpannableStringBuilder +import com.margelo.nitro.nitrotext.RichTextStyle +import com.margelo.nitro.nitrotext.RichTextStyleRule + +interface NitroRendererInterface { + fun render(input: String, baseStyle: RichTextStyle?, rules: Array?): SpannableStringBuilder +} \ No newline at end of file diff --git a/android/src/main/java/com/nitrotext/spans/BulletListSpan.kt b/android/src/main/java/com/nitrotext/spans/BulletListSpan.kt new file mode 100644 index 0000000..31f236c --- /dev/null +++ b/android/src/main/java/com/nitrotext/spans/BulletListSpan.kt @@ -0,0 +1,48 @@ +package com.nitrotext.spans + +import android.graphics.Canvas +import android.graphics.Paint +import android.text.style.LeadingMarginSpan + +internal class BulletListSpan( + private val leadingMargin: Int, + private val gapWidth: Int, + private val radiusPx: Float +) : LeadingMarginSpan.LeadingMarginSpan2 { + + override fun getLeadingMargin(first: Boolean): Int = leadingMargin + + override fun getLeadingMarginLineCount(): Int = 1 + + override fun drawLeadingMargin( + canvas: Canvas, + paint: Paint, + x: Int, + dir: Int, + top: Int, + baseline: Int, + bottom: Int, + text: CharSequence, + start: Int, + end: Int, + first: Boolean, + layout: android.text.Layout? + ) { + if (!first || radiusPx <= 0f) return + val previousStyle = paint.style + val previousColor = paint.color + paint.style = Paint.Style.FILL + + val gap = gapWidth.toFloat() / 2f + val centerX = if (dir > 0) { + x + radiusPx + gap + } else { + x - radiusPx - gap + } + val centerY = (top + bottom) / 2f + canvas.drawCircle(centerX, centerY, radiusPx, paint) + + paint.style = previousStyle + paint.color = previousColor + } +} diff --git a/android/src/main/java/com/nitrotext/spans/LetterSpacingSpan.kt b/android/src/main/java/com/nitrotext/spans/LetterSpacingSpan.kt new file mode 100644 index 0000000..7d2ea71 --- /dev/null +++ b/android/src/main/java/com/nitrotext/spans/LetterSpacingSpan.kt @@ -0,0 +1,14 @@ +package com.nitrotext.spans + +import android.text.TextPaint +import android.text.style.MetricAffectingSpan + +internal class LetterSpacingSpan(private val em: Float) : MetricAffectingSpan() { + override fun updateDrawState(tp: TextPaint) { + tp.letterSpacing = em + } + + override fun updateMeasureState(tp: TextPaint) { + tp.letterSpacing = em + } +} \ No newline at end of file diff --git a/android/src/main/java/com/nitrotext/spans/NitroLineHeightSpan.kt b/android/src/main/java/com/nitrotext/spans/NitroLineHeightSpan.kt new file mode 100644 index 0000000..15c3992 --- /dev/null +++ b/android/src/main/java/com/nitrotext/spans/NitroLineHeightSpan.kt @@ -0,0 +1,29 @@ +package com.nitrotext.spans + +import android.graphics.Paint +import android.text.style.LineHeightSpan +import kotlin.math.ceil +import kotlin.math.floor + +/** + * Matches React Native's CustomLineHeightSpan so lineHeight behaves the same as . + */ +internal class NitroLineHeightSpan(heightPx: Float) : LineHeightSpan { + private val lineHeight: Int = ceil(heightPx.toDouble()).toInt() + + override fun chooseHeight( + text: CharSequence, + start: Int, + end: Int, + spanstartv: Int, + v: Int, + fm: Paint.FontMetricsInt + ) { + val leading = lineHeight - ((-fm.ascent) + fm.descent) + val halfLeading = leading / 2f + fm.ascent -= ceil(halfLeading.toDouble()).toInt() + fm.descent += floor(halfLeading.toDouble()).toInt() + if (start == 0) fm.top = fm.ascent + if (end == text.length) fm.bottom = fm.descent + } +} diff --git a/android/src/main/java/com/nitrotext/spans/NumberedListSpan.kt b/android/src/main/java/com/nitrotext/spans/NumberedListSpan.kt new file mode 100644 index 0000000..f23872f --- /dev/null +++ b/android/src/main/java/com/nitrotext/spans/NumberedListSpan.kt @@ -0,0 +1,49 @@ +package com.nitrotext.spans + +import android.graphics.Canvas +import android.graphics.Paint +import android.text.style.LeadingMarginSpan + +internal class NumberedListSpan( + private val number: Int, + private val leadingMargin: Int, + private val gapWidth: Int +) : LeadingMarginSpan.LeadingMarginSpan2 { + + override fun getLeadingMargin(first: Boolean): Int = leadingMargin + + override fun getLeadingMarginLineCount(): Int = 1 + + override fun drawLeadingMargin( + canvas: Canvas, + paint: Paint, + x: Int, + dir: Int, + top: Int, + baseline: Int, + bottom: Int, + text: CharSequence, + start: Int, + end: Int, + first: Boolean, + layout: android.text.Layout? + ) { + if (!first || number <= 0) return + val label = "$number." + val previousStyle = paint.style + paint.style = Paint.Style.FILL + + val textStart = x + dir * leadingMargin + val labelWidth = paint.measureText(label) + val gap = gapWidth.toFloat() + val labelX = if (dir > 0) { + textStart - gap - labelWidth + } else { + textStart + gap + } + + canvas.drawText(label, labelX, baseline.toFloat(), paint) + + paint.style = previousStyle + } +} diff --git a/android/src/main/java/com/nitrotext/spans/UrlSpanNoUnderline.kt b/android/src/main/java/com/nitrotext/spans/UrlSpanNoUnderline.kt new file mode 100644 index 0000000..8db1e62 --- /dev/null +++ b/android/src/main/java/com/nitrotext/spans/UrlSpanNoUnderline.kt @@ -0,0 +1,11 @@ +package com.nitrotext.spans + +import android.text.TextPaint +import android.text.style.URLSpan + +internal class UrlSpanNoUnderline(url: String) : URLSpan(url) { + override fun updateDrawState(ds: TextPaint) { + super.updateDrawState(ds) + ds.isUnderlineText = false + } +} diff --git a/android/src/main/java/com/nitrotext/spans/VerticalMarginSpan.kt b/android/src/main/java/com/nitrotext/spans/VerticalMarginSpan.kt new file mode 100644 index 0000000..52a2955 --- /dev/null +++ b/android/src/main/java/com/nitrotext/spans/VerticalMarginSpan.kt @@ -0,0 +1,31 @@ +package com.nitrotext.spans + +import android.graphics.Paint +import android.text.style.LineHeightSpan +import kotlin.math.roundToInt + +internal class VerticalMarginSpan( + private val top: Float, + private val bottom: Float +) : LineHeightSpan { + override fun chooseHeight( + text: CharSequence?, + start: Int, + end: Int, + spanstartv: Int, + v: Int, + fm: Paint.FontMetricsInt? + ) { + fm ?: return + val addTop = top.roundToInt() + val addBottom = bottom.roundToInt() + if (addTop != 0) { + fm.top -= addTop + fm.ascent -= addTop + } + if (addBottom != 0) { + fm.bottom += addBottom + fm.descent += addBottom + } + } +} diff --git a/cpp/NitroHtmlUtils.cpp b/cpp/NitroHtmlUtils.cpp new file mode 100644 index 0000000..ca640cb --- /dev/null +++ b/cpp/NitroHtmlUtils.cpp @@ -0,0 +1,272 @@ +// +// NitroHtmlUtils.cpp +// + +#include "NitroHtmlUtils.hpp" + +#include +#include +#include + +namespace margelo::nitro::nitrotext::html { + +std::string NitroHtmlUtils::stripTags(std::string_view input) +{ + std::string output; + output.reserve(input.size()); + + bool inTag = false; + bool preserveWhitespace = false; + bool lastWasWhitespace = true; + std::string tagBuffer; + + auto appendChar = [&](char ch) { + if (preserveWhitespace) + { + output.push_back(ch); + lastWasWhitespace = std::isspace(static_cast(ch)); + return; + } + + if (ch == '\r') + { + return; + } + + if (ch == '\n') + { + if (!output.empty() && output.back() == '\n') + { + lastWasWhitespace = true; + return; + } + if (!output.empty() && output.back() == ' ') + { + output.pop_back(); + } + output.push_back('\n'); + lastWasWhitespace = true; + return; + } + + if (std::isspace(static_cast(ch))) + { + if (!lastWasWhitespace) + { + output.push_back(' '); + lastWasWhitespace = true; + } + return; + } + + output.push_back(ch); + lastWasWhitespace = false; + }; + + const auto isBlockLevel = [](std::string_view tagName) { + static constexpr std::array kBlockTags = { + "address", + "article", + "aside", + "blockquote", + "div", + "dl", + "fieldset", + "figcaption", + "figure", + "footer", + "form", + "h1", + "h2", + "h3", + "h4", + "h5", + "h6", + "header", + "li", + "main", + "nav", + "ol", + "p", + "pre", + "section", + "table", + "ul"}; + + return std::find(kBlockTags.begin(), kBlockTags.end(), tagName) != kBlockTags.end(); + }; + + auto flushTag = [&](std::string_view tagView) { + const auto first = tagView.find_first_not_of(" \t\n\r"); + if (first == std::string_view::npos) + { + return; + } + tagView.remove_prefix(first); + + const auto last = tagView.find_last_not_of(" \t\n\r"); + if (last != std::string_view::npos && last + 1 < tagView.size()) + { + tagView.remove_suffix(tagView.size() - last - 1); + } + + if (tagView.size() >= 3 && tagView.substr(0, 3) == "!--") + { + return; + } + + bool isClosing = false; + if (!tagView.empty() && tagView.front() == '/') + { + isClosing = true; + tagView.remove_prefix(1); + const auto notSpace = tagView.find_first_not_of(" \t\n\r"); + if (notSpace == std::string_view::npos) + { + return; + } + tagView.remove_prefix(notSpace); + } + + bool isSelfClosing = false; + if (!tagView.empty() && tagView.back() == '/') + { + isSelfClosing = true; + tagView.remove_suffix(1); + const auto notSpace = tagView.find_last_not_of(" \t\n\r"); + if (notSpace == std::string_view::npos) + { + return; + } + if (notSpace + 1 < tagView.size()) + { + tagView.remove_suffix(tagView.size() - notSpace - 1); + } + } + + const auto spacePos = tagView.find_first_of(" \t\n\r"); + std::string_view tagNameView = + spacePos == std::string_view::npos ? tagView : tagView.substr(0, spacePos); + if (tagNameView.empty()) + { + return; + } + + std::string tagName(tagNameView); + std::transform(tagName.begin(), tagName.end(), tagName.begin(), [](unsigned char ch) { + return static_cast(std::tolower(ch)); + }); + + if (!isClosing && !isSelfClosing && tagName == "pre") + { + preserveWhitespace = true; + lastWasWhitespace = true; + } + + if (isClosing && tagName == "pre") + { + preserveWhitespace = false; + appendChar('\n'); + return; + } + + if (tagName == "br") + { + appendChar('\n'); + return; + } + + if (isClosing && isBlockLevel(tagName)) + { + appendChar('\n'); + } + else if (!isClosing && !isSelfClosing && isBlockLevel(tagName) && !output.empty() && output.back() != '\n') + { + appendChar('\n'); + } + }; + + auto decodeEntity = [&](std::string_view entity) { + if (entity == "nbsp") + { + appendChar(' '); + } + else if (entity == "amp") + { + appendChar('&'); + } + else if (entity == "lt") + { + appendChar('<'); + } + else if (entity == "gt") + { + appendChar('>'); + } + else if (entity == "quot") + { + appendChar('"'); + } + else if (entity == "apos") + { + appendChar(static_cast(0x27)); + } + else + { + appendChar('&'); + for (const char ch : entity) + { + appendChar(ch); + } + appendChar(';'); + } + }; + + for (size_t i = 0; i < input.size(); ++i) + { + const char c = input[i]; + if (inTag) + { + if (c == '>') + { + inTag = false; + flushTag(std::string_view{tagBuffer}); + tagBuffer.clear(); + } + else + { + tagBuffer.push_back(c); + } + continue; + } + + if (c == '<') + { + inTag = true; + tagBuffer.clear(); + continue; + } + + if (c == '&') + { + const auto semicolon = input.find(';', i + 1); + if (semicolon != std::string::npos) + { + decodeEntity(input.substr(i + 1, semicolon - (i + 1))); + i = semicolon; + continue; + } + } + + appendChar(c); + } + + while (!output.empty() && (output.back() == ' ' || output.back() == '\n')) + { + output.pop_back(); + } + + return output; +} + +} // namespace margelo::nitro::nitrotext::html diff --git a/cpp/NitroHtmlUtils.hpp b/cpp/NitroHtmlUtils.hpp new file mode 100644 index 0000000..1f5b9ab --- /dev/null +++ b/cpp/NitroHtmlUtils.hpp @@ -0,0 +1,19 @@ +// +// NitroHtmlUtils.hpp +// + +#pragma once + +#include +#include + +namespace margelo::nitro::nitrotext::html { + +class NitroHtmlUtils +{ +public: + static std::string stripTags(std::string_view input); +}; + +} // namespace margelo::nitro::nitrotext::html + diff --git a/cpp/NitroTextShadowNode.cpp b/cpp/NitroTextShadowNode.cpp index 75770ab..7380cc3 100644 --- a/cpp/NitroTextShadowNode.cpp +++ b/cpp/NitroTextShadowNode.cpp @@ -5,11 +5,17 @@ #include "NitroTextShadowNode.hpp" +#include "NitroHtmlUtils.hpp" + #include #include #include #include +#if defined(__ANDROID__) +#include +#endif + #include #if __has_include() @@ -46,6 +52,9 @@ react::Size NitroTextShadowNode::measureContent( const react::LayoutConstraints &layoutConstraints) const { const auto &props = this->getConcreteProps(); + using NitroRenderer = margelo::nitro::nitrotext::NitroRenderer; + const bool isHtmlRenderer = props.renderer.value.has_value() && + props.renderer.value.value() == NitroRenderer::HTML; auto makeTextAttributes = [&](const std::optional &fragOpt) { @@ -335,11 +344,14 @@ react::Size NitroTextShadowNode::measureContent( for (const auto &f : frags) { const std::string fragmentText = f.text.has_value() ? f.text.value() : std::string(""); - if (fragmentText.empty()) + const std::string sanitizedFragmentText = + isHtmlRenderer ? html::NitroHtmlUtils::stripTags(fragmentText) + : fragmentText; + if (sanitizedFragmentText.empty()) continue; auto attrs = makeTextAttributes(f); attributedString.appendFragment(react::AttributedString::Fragment{ - .string = fragmentText, + .string = sanitizedFragmentText, .textAttributes = attrs, .parentShadowView = react::ShadowView(*this)}); } @@ -347,9 +359,28 @@ react::Size NitroTextShadowNode::measureContent( else { const std::string textToMeasure = props.text.value.has_value() ? props.text.value.value() : std::string(""); + const std::string sanitizedText = + isHtmlRenderer ? html::NitroHtmlUtils::stripTags(textToMeasure) + : textToMeasure; +#if defined(__ANDROID__) +#if defined(__FILE_NAME__) + constexpr const char *kFileName = __FILE_NAME__; +#else + constexpr const char *kFileName = __FILE__; +#endif + __android_log_print( + ANDROID_LOG_INFO, + "NitroTextShadowNode", + "%s:%d textToMeasure: %s", + kFileName, + __LINE__, + sanitizedText.c_str()); +#else + LOG(INFO) << "textToMeasure: " << sanitizedText; +#endif auto attrs = makeTextAttributes(std::nullopt); attributedString.appendFragment(react::AttributedString::Fragment{ - .string = textToMeasure, + .string = sanitizedText, .textAttributes = attrs, .parentShadowView = react::ShadowView(*this)}); } diff --git a/example/App.tsx b/example/App.tsx index 283506d..c349fce 100644 --- a/example/App.tsx +++ b/example/App.tsx @@ -1,7 +1,9 @@ -import React, { useState } from 'react'; -import { ScrollView, StyleSheet, Text, View } from 'react-native'; +import React, { useLayoutEffect, useState } from 'react'; +import { ScrollView, StatusBar, StyleSheet, Text, View } from 'react-native'; import { NitroText, TextLayoutEvent } from 'react-native-nitro-text'; +const htmlComingFromServer = `

This is a h1 text

This is a h2 text

This is a h3 text

This is a h4 text

This is a h5 text
This is a h6 text

This is a simple NitroText component with native performance. Try selecting this text to see the smooth selection behavior!

Google Link
This is a bold text
This is an italic text
This is an underline text
This is a strikethrough text
This is a code text
This is a pre text

`.trim(); + export default function App() { const [layoutInfo, setLayoutInfo] = useState(''); @@ -17,6 +19,10 @@ export default function App() { // setLayoutInfo(`Lines: ${lines.length}`); }; + useLayoutEffect(() => { + StatusBar.setBarStyle('dark-content'); + }, []); + return ( {/* Header Section */} @@ -48,6 +54,34 @@ export default function App() {
+ {/* Html Renderer */} + + Html Renderer + + {htmlComingFromServer} + + + {/* Nested NitroText wth numberOfLines (does not work currently it only renders the first line nested text doesn't render) */} @@ -143,7 +177,11 @@ export default function App() { Line Limiting Two lines maximum: - + This is a very long text that would normally span multiple lines, but we're limiting it to just two lines. The text will be truncated with an ellipsis when it exceeds the specified number of lines. This is @@ -166,7 +204,7 @@ export default function App() { {'\n\n'}And vice versa - NitroText can contain:{'\n'} Regular text with - {' '}RN Text nested inside{' '} + RN Text nested inside{' '} NitroText. @@ -311,6 +349,18 @@ const styles = StyleSheet.create({ borderLeftColor: '#007bff', }, + htmlText: { + fontSize: 16, + color: '#495057', + lineHeight: 24, + backgroundColor: '#ffffff', + padding: 16, + borderRadius: 8, + borderLeftWidth: 4, + borderLeftColor: '#007bff', + // height:450 + }, + // Rich text styles richText: { fontSize: 16, diff --git a/ios/HybridNitroText.swift b/ios/HybridNitroText.swift index 8ac0adc..662cd1c 100755 --- a/ios/HybridNitroText.swift +++ b/ios/HybridNitroText.swift @@ -34,6 +34,19 @@ class HybridNitroText: HybridNitroTextSpec, NitroTextViewDelegate { didSet { markNeedsApply() } } + var renderer: NitroRenderer? { + didSet { + // HTML rendering is currently implemented on Android only. + // Keep tracking the requested renderer to avoid dropping the prop. + } + } + + var richTextStyleRules: [RichTextStyleRule]? { + didSet { + // No-op on iOS for now. + } + } + var selectable: Bool? { didSet { nitroTextImpl.setSelectable(selectable) diff --git a/nitrogen/generated/android/c++/JHybridNitroTextSpec.cpp b/nitrogen/generated/android/c++/JHybridNitroTextSpec.cpp index 9e036bb..9421793 100644 --- a/nitrogen/generated/android/c++/JHybridNitroTextSpec.cpp +++ b/nitrogen/generated/android/c++/JHybridNitroTextSpec.cpp @@ -21,6 +21,12 @@ namespace margelo::nitro::nitrotext { enum class TextTransform; } namespace margelo::nitro::nitrotext { enum class TextDecorationLine; } // Forward declaration of `TextDecorationStyle` to properly resolve imports. namespace margelo::nitro::nitrotext { enum class TextDecorationStyle; } +// Forward declaration of `NitroRenderer` to properly resolve imports. +namespace margelo::nitro::nitrotext { enum class NitroRenderer; } +// Forward declaration of `RichTextStyleRule` to properly resolve imports. +namespace margelo::nitro::nitrotext { struct RichTextStyleRule; } +// Forward declaration of `RichTextStyle` to properly resolve imports. +namespace margelo::nitro::nitrotext { struct RichTextStyle; } // Forward declaration of `EllipsizeMode` to properly resolve imports. namespace margelo::nitro::nitrotext { enum class EllipsizeMode; } // Forward declaration of `LineBreakStrategyIOS` to properly resolve imports. @@ -49,6 +55,12 @@ namespace margelo::nitro::nitrotext { struct TextLayout; } #include "JTextDecorationLine.hpp" #include "TextDecorationStyle.hpp" #include "JTextDecorationStyle.hpp" +#include "NitroRenderer.hpp" +#include "JNitroRenderer.hpp" +#include "RichTextStyleRule.hpp" +#include "JRichTextStyleRule.hpp" +#include "RichTextStyle.hpp" +#include "JRichTextStyle.hpp" #include "EllipsizeMode.hpp" #include "JEllipsizeMode.hpp" #include "LineBreakStrategyIOS.hpp" @@ -112,6 +124,41 @@ namespace margelo::nitro::nitrotext { return __array; }() : nullptr); } + std::optional JHybridNitroTextSpec::getRenderer() { + static const auto method = javaClassStatic()->getMethod()>("getRenderer"); + auto __result = method(_javaPart); + return __result != nullptr ? std::make_optional(__result->toCpp()) : std::nullopt; + } + void JHybridNitroTextSpec::setRenderer(std::optional renderer) { + static const auto method = javaClassStatic()->getMethod /* renderer */)>("setRenderer"); + method(_javaPart, renderer.has_value() ? JNitroRenderer::fromCpp(renderer.value()) : nullptr); + } + std::optional> JHybridNitroTextSpec::getRichTextStyleRules() { + static const auto method = javaClassStatic()->getMethod>()>("getRichTextStyleRules"); + auto __result = method(_javaPart); + return __result != nullptr ? std::make_optional([&]() { + size_t __size = __result->size(); + std::vector __vector; + __vector.reserve(__size); + for (size_t __i = 0; __i < __size; __i++) { + auto __element = __result->getElement(__i); + __vector.push_back(__element->toCpp()); + } + return __vector; + }()) : std::nullopt; + } + void JHybridNitroTextSpec::setRichTextStyleRules(const std::optional>& richTextStyleRules) { + static const auto method = javaClassStatic()->getMethod> /* richTextStyleRules */)>("setRichTextStyleRules"); + method(_javaPart, richTextStyleRules.has_value() ? [&]() { + size_t __size = richTextStyleRules.value().size(); + jni::local_ref> __array = jni::JArrayClass::newArray(__size); + for (size_t __i = 0; __i < __size; __i++) { + const auto& __element = richTextStyleRules.value()[__i]; + __array->setElement(__i, *JRichTextStyleRule::fromCpp(__element)); + } + return __array; + }() : nullptr); + } std::optional JHybridNitroTextSpec::getSelectable() { static const auto method = javaClassStatic()->getMethod()>("getSelectable"); auto __result = method(_javaPart); diff --git a/nitrogen/generated/android/c++/JHybridNitroTextSpec.hpp b/nitrogen/generated/android/c++/JHybridNitroTextSpec.hpp index 6c2a518..90196c2 100644 --- a/nitrogen/generated/android/c++/JHybridNitroTextSpec.hpp +++ b/nitrogen/generated/android/c++/JHybridNitroTextSpec.hpp @@ -51,6 +51,10 @@ namespace margelo::nitro::nitrotext { // Properties std::optional> getFragments() override; void setFragments(const std::optional>& fragments) override; + std::optional getRenderer() override; + void setRenderer(std::optional renderer) override; + std::optional> getRichTextStyleRules() override; + void setRichTextStyleRules(const std::optional>& richTextStyleRules) override; std::optional getSelectable() override; void setSelectable(std::optional selectable) override; std::optional getAllowFontScaling() override; diff --git a/nitrogen/generated/android/c++/JNitroRenderer.hpp b/nitrogen/generated/android/c++/JNitroRenderer.hpp new file mode 100644 index 0000000..7350303 --- /dev/null +++ b/nitrogen/generated/android/c++/JNitroRenderer.hpp @@ -0,0 +1,59 @@ +/// +/// JNitroRenderer.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#pragma once + +#include +#include "NitroRenderer.hpp" + +namespace margelo::nitro::nitrotext { + + using namespace facebook; + + /** + * The C++ JNI bridge between the C++ enum "NitroRenderer" and the the Kotlin enum "NitroRenderer". + */ + struct JNitroRenderer final: public jni::JavaClass { + public: + static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/nitrotext/NitroRenderer;"; + + public: + /** + * Convert this Java/Kotlin-based enum to the C++ enum NitroRenderer. + */ + [[maybe_unused]] + [[nodiscard]] + NitroRenderer toCpp() const { + static const auto clazz = javaClassStatic(); + static const auto fieldOrdinal = clazz->getField("value"); + int ordinal = this->getFieldValue(fieldOrdinal); + return static_cast(ordinal); + } + + public: + /** + * Create a Java/Kotlin-based enum with the given C++ enum's value. + */ + [[maybe_unused]] + static jni::alias_ref fromCpp(NitroRenderer value) { + static const auto clazz = javaClassStatic(); + static const auto fieldPLAINTEXT = clazz->getStaticField("PLAINTEXT"); + static const auto fieldHTML = clazz->getStaticField("HTML"); + + switch (value) { + case NitroRenderer::PLAINTEXT: + return clazz->getStaticFieldValue(fieldPLAINTEXT); + case NitroRenderer::HTML: + return clazz->getStaticFieldValue(fieldHTML); + default: + std::string stringValue = std::to_string(static_cast(value)); + throw std::invalid_argument("Invalid enum value (" + stringValue + "!"); + } + } + }; + +} // namespace margelo::nitro::nitrotext diff --git a/nitrogen/generated/android/c++/JRichTextStyle.hpp b/nitrogen/generated/android/c++/JRichTextStyle.hpp new file mode 100644 index 0000000..b95ef09 --- /dev/null +++ b/nitrogen/generated/android/c++/JRichTextStyle.hpp @@ -0,0 +1,130 @@ +/// +/// JRichTextStyle.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#pragma once + +#include +#include "RichTextStyle.hpp" + +#include "FontStyle.hpp" +#include "FontWeight.hpp" +#include "JFontStyle.hpp" +#include "JFontWeight.hpp" +#include "JTextAlign.hpp" +#include "JTextDecorationLine.hpp" +#include "JTextDecorationStyle.hpp" +#include "JTextTransform.hpp" +#include "TextAlign.hpp" +#include "TextDecorationLine.hpp" +#include "TextDecorationStyle.hpp" +#include "TextTransform.hpp" +#include +#include + +namespace margelo::nitro::nitrotext { + + using namespace facebook; + + /** + * The C++ JNI bridge between the C++ struct "RichTextStyle" and the the Kotlin data class "RichTextStyle". + */ + struct JRichTextStyle final: public jni::JavaClass { + public: + static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/nitrotext/RichTextStyle;"; + + public: + /** + * Convert this Java/Kotlin-based struct to the C++ struct RichTextStyle by copying all values to C++. + */ + [[maybe_unused]] + [[nodiscard]] + RichTextStyle toCpp() const { + static const auto clazz = javaClassStatic(); + static const auto fieldFontColor = clazz->getField("fontColor"); + jni::local_ref fontColor = this->getFieldValue(fieldFontColor); + static const auto fieldFragmentBackgroundColor = clazz->getField("fragmentBackgroundColor"); + jni::local_ref fragmentBackgroundColor = this->getFieldValue(fieldFragmentBackgroundColor); + static const auto fieldFontSize = clazz->getField("fontSize"); + jni::local_ref fontSize = this->getFieldValue(fieldFontSize); + static const auto fieldFontWeight = clazz->getField("fontWeight"); + jni::local_ref fontWeight = this->getFieldValue(fieldFontWeight); + static const auto fieldFontStyle = clazz->getField("fontStyle"); + jni::local_ref fontStyle = this->getFieldValue(fieldFontStyle); + static const auto fieldFontFamily = clazz->getField("fontFamily"); + jni::local_ref fontFamily = this->getFieldValue(fieldFontFamily); + static const auto fieldLineHeight = clazz->getField("lineHeight"); + jni::local_ref lineHeight = this->getFieldValue(fieldLineHeight); + static const auto fieldLetterSpacing = clazz->getField("letterSpacing"); + jni::local_ref letterSpacing = this->getFieldValue(fieldLetterSpacing); + static const auto fieldTextAlign = clazz->getField("textAlign"); + jni::local_ref textAlign = this->getFieldValue(fieldTextAlign); + static const auto fieldTextTransform = clazz->getField("textTransform"); + jni::local_ref textTransform = this->getFieldValue(fieldTextTransform); + static const auto fieldTextDecorationLine = clazz->getField("textDecorationLine"); + jni::local_ref textDecorationLine = this->getFieldValue(fieldTextDecorationLine); + static const auto fieldTextDecorationColor = clazz->getField("textDecorationColor"); + jni::local_ref textDecorationColor = this->getFieldValue(fieldTextDecorationColor); + static const auto fieldTextDecorationStyle = clazz->getField("textDecorationStyle"); + jni::local_ref textDecorationStyle = this->getFieldValue(fieldTextDecorationStyle); + static const auto fieldMarginTop = clazz->getField("marginTop"); + jni::local_ref marginTop = this->getFieldValue(fieldMarginTop); + static const auto fieldMarginBottom = clazz->getField("marginBottom"); + jni::local_ref marginBottom = this->getFieldValue(fieldMarginBottom); + static const auto fieldMarginLeft = clazz->getField("marginLeft"); + jni::local_ref marginLeft = this->getFieldValue(fieldMarginLeft); + static const auto fieldMarginRight = clazz->getField("marginRight"); + jni::local_ref marginRight = this->getFieldValue(fieldMarginRight); + return RichTextStyle( + fontColor != nullptr ? std::make_optional(fontColor->toStdString()) : std::nullopt, + fragmentBackgroundColor != nullptr ? std::make_optional(fragmentBackgroundColor->toStdString()) : std::nullopt, + fontSize != nullptr ? std::make_optional(fontSize->value()) : std::nullopt, + fontWeight != nullptr ? std::make_optional(fontWeight->toCpp()) : std::nullopt, + fontStyle != nullptr ? std::make_optional(fontStyle->toCpp()) : std::nullopt, + fontFamily != nullptr ? std::make_optional(fontFamily->toStdString()) : std::nullopt, + lineHeight != nullptr ? std::make_optional(lineHeight->value()) : std::nullopt, + letterSpacing != nullptr ? std::make_optional(letterSpacing->value()) : std::nullopt, + textAlign != nullptr ? std::make_optional(textAlign->toCpp()) : std::nullopt, + textTransform != nullptr ? std::make_optional(textTransform->toCpp()) : std::nullopt, + textDecorationLine != nullptr ? std::make_optional(textDecorationLine->toCpp()) : std::nullopt, + textDecorationColor != nullptr ? std::make_optional(textDecorationColor->toStdString()) : std::nullopt, + textDecorationStyle != nullptr ? std::make_optional(textDecorationStyle->toCpp()) : std::nullopt, + marginTop != nullptr ? std::make_optional(marginTop->value()) : std::nullopt, + marginBottom != nullptr ? std::make_optional(marginBottom->value()) : std::nullopt, + marginLeft != nullptr ? std::make_optional(marginLeft->value()) : std::nullopt, + marginRight != nullptr ? std::make_optional(marginRight->value()) : std::nullopt + ); + } + + public: + /** + * Create a Java/Kotlin-based struct by copying all values from the given C++ struct to Java. + */ + [[maybe_unused]] + static jni::local_ref fromCpp(const RichTextStyle& value) { + return newInstance( + value.fontColor.has_value() ? jni::make_jstring(value.fontColor.value()) : nullptr, + value.fragmentBackgroundColor.has_value() ? jni::make_jstring(value.fragmentBackgroundColor.value()) : nullptr, + value.fontSize.has_value() ? jni::JDouble::valueOf(value.fontSize.value()) : nullptr, + value.fontWeight.has_value() ? JFontWeight::fromCpp(value.fontWeight.value()) : nullptr, + value.fontStyle.has_value() ? JFontStyle::fromCpp(value.fontStyle.value()) : nullptr, + value.fontFamily.has_value() ? jni::make_jstring(value.fontFamily.value()) : nullptr, + value.lineHeight.has_value() ? jni::JDouble::valueOf(value.lineHeight.value()) : nullptr, + value.letterSpacing.has_value() ? jni::JDouble::valueOf(value.letterSpacing.value()) : nullptr, + value.textAlign.has_value() ? JTextAlign::fromCpp(value.textAlign.value()) : nullptr, + value.textTransform.has_value() ? JTextTransform::fromCpp(value.textTransform.value()) : nullptr, + value.textDecorationLine.has_value() ? JTextDecorationLine::fromCpp(value.textDecorationLine.value()) : nullptr, + value.textDecorationColor.has_value() ? jni::make_jstring(value.textDecorationColor.value()) : nullptr, + value.textDecorationStyle.has_value() ? JTextDecorationStyle::fromCpp(value.textDecorationStyle.value()) : nullptr, + value.marginTop.has_value() ? jni::JDouble::valueOf(value.marginTop.value()) : nullptr, + value.marginBottom.has_value() ? jni::JDouble::valueOf(value.marginBottom.value()) : nullptr, + value.marginLeft.has_value() ? jni::JDouble::valueOf(value.marginLeft.value()) : nullptr, + value.marginRight.has_value() ? jni::JDouble::valueOf(value.marginRight.value()) : nullptr + ); + } + }; + +} // namespace margelo::nitro::nitrotext diff --git a/nitrogen/generated/android/c++/JRichTextStyleRule.hpp b/nitrogen/generated/android/c++/JRichTextStyleRule.hpp new file mode 100644 index 0000000..4cc9d71 --- /dev/null +++ b/nitrogen/generated/android/c++/JRichTextStyleRule.hpp @@ -0,0 +1,72 @@ +/// +/// JRichTextStyleRule.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#pragma once + +#include +#include "RichTextStyleRule.hpp" + +#include "FontStyle.hpp" +#include "FontWeight.hpp" +#include "JFontStyle.hpp" +#include "JFontWeight.hpp" +#include "JRichTextStyle.hpp" +#include "JTextAlign.hpp" +#include "JTextDecorationLine.hpp" +#include "JTextDecorationStyle.hpp" +#include "JTextTransform.hpp" +#include "RichTextStyle.hpp" +#include "TextAlign.hpp" +#include "TextDecorationLine.hpp" +#include "TextDecorationStyle.hpp" +#include "TextTransform.hpp" +#include +#include + +namespace margelo::nitro::nitrotext { + + using namespace facebook; + + /** + * The C++ JNI bridge between the C++ struct "RichTextStyleRule" and the the Kotlin data class "RichTextStyleRule". + */ + struct JRichTextStyleRule final: public jni::JavaClass { + public: + static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/nitrotext/RichTextStyleRule;"; + + public: + /** + * Convert this Java/Kotlin-based struct to the C++ struct RichTextStyleRule by copying all values to C++. + */ + [[maybe_unused]] + [[nodiscard]] + RichTextStyleRule toCpp() const { + static const auto clazz = javaClassStatic(); + static const auto fieldSelector = clazz->getField("selector"); + jni::local_ref selector = this->getFieldValue(fieldSelector); + static const auto fieldStyle = clazz->getField("style"); + jni::local_ref style = this->getFieldValue(fieldStyle); + return RichTextStyleRule( + selector->toStdString(), + style->toCpp() + ); + } + + public: + /** + * Create a Java/Kotlin-based struct by copying all values from the given C++ struct to Java. + */ + [[maybe_unused]] + static jni::local_ref fromCpp(const RichTextStyleRule& value) { + return newInstance( + jni::make_jstring(value.selector), + JRichTextStyle::fromCpp(value.style) + ); + } + }; + +} // namespace margelo::nitro::nitrotext diff --git a/nitrogen/generated/android/c++/views/JHybridNitroTextStateUpdater.cpp b/nitrogen/generated/android/c++/views/JHybridNitroTextStateUpdater.cpp index 2ce0684..b9ed656 100644 --- a/nitrogen/generated/android/c++/views/JHybridNitroTextStateUpdater.cpp +++ b/nitrogen/generated/android/c++/views/JHybridNitroTextStateUpdater.cpp @@ -40,6 +40,14 @@ void JHybridNitroTextStateUpdater::updateViewProps(jni::alias_ref / view->setFragments(props.fragments.value); // TODO: Set isDirty = false } + if (props.renderer.isDirty) { + view->setRenderer(props.renderer.value); + // TODO: Set isDirty = false + } + if (props.richTextStyleRules.isDirty) { + view->setRichTextStyleRules(props.richTextStyleRules.value); + // TODO: Set isDirty = false + } if (props.selectable.isDirty) { view->setSelectable(props.selectable.value); // TODO: Set isDirty = false diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/HybridNitroTextSpec.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/HybridNitroTextSpec.kt index 88ddc86..4984833 100644 --- a/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/HybridNitroTextSpec.kt +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/HybridNitroTextSpec.kt @@ -44,6 +44,18 @@ abstract class HybridNitroTextSpec: HybridView() { @set:Keep abstract var fragments: Array? + @get:DoNotStrip + @get:Keep + @set:DoNotStrip + @set:Keep + abstract var renderer: NitroRenderer? + + @get:DoNotStrip + @get:Keep + @set:DoNotStrip + @set:Keep + abstract var richTextStyleRules: Array? + @get:DoNotStrip @get:Keep @set:DoNotStrip diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/NitroRenderer.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/NitroRenderer.kt new file mode 100644 index 0000000..89bf445 --- /dev/null +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/NitroRenderer.kt @@ -0,0 +1,21 @@ +/// +/// NitroRenderer.kt +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +package com.margelo.nitro.nitrotext + +import androidx.annotation.Keep +import com.facebook.proguard.annotations.DoNotStrip + +/** + * Represents the JavaScript enum/union "NitroRenderer". + */ +@DoNotStrip +@Keep +enum class NitroRenderer(@DoNotStrip @Keep val value: Int) { + PLAINTEXT(0), + HTML(1); +} diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/RichTextStyle.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/RichTextStyle.kt new file mode 100644 index 0000000..b548d2f --- /dev/null +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/RichTextStyle.kt @@ -0,0 +1,77 @@ +/// +/// RichTextStyle.kt +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +package com.margelo.nitro.nitrotext + +import androidx.annotation.Keep +import com.facebook.proguard.annotations.DoNotStrip +import com.margelo.nitro.core.* + + +/** + * Represents the JavaScript object/struct "RichTextStyle". + */ +@DoNotStrip +@Keep +data class RichTextStyle + @DoNotStrip + @Keep + constructor( + @DoNotStrip + @Keep + val fontColor: String?, + @DoNotStrip + @Keep + val fragmentBackgroundColor: String?, + @DoNotStrip + @Keep + val fontSize: Double?, + @DoNotStrip + @Keep + val fontWeight: FontWeight?, + @DoNotStrip + @Keep + val fontStyle: FontStyle?, + @DoNotStrip + @Keep + val fontFamily: String?, + @DoNotStrip + @Keep + val lineHeight: Double?, + @DoNotStrip + @Keep + val letterSpacing: Double?, + @DoNotStrip + @Keep + val textAlign: TextAlign?, + @DoNotStrip + @Keep + val textTransform: TextTransform?, + @DoNotStrip + @Keep + val textDecorationLine: TextDecorationLine?, + @DoNotStrip + @Keep + val textDecorationColor: String?, + @DoNotStrip + @Keep + val textDecorationStyle: TextDecorationStyle?, + @DoNotStrip + @Keep + val marginTop: Double?, + @DoNotStrip + @Keep + val marginBottom: Double?, + @DoNotStrip + @Keep + val marginLeft: Double?, + @DoNotStrip + @Keep + val marginRight: Double? + ) { + /* main constructor */ +} diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/RichTextStyleRule.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/RichTextStyleRule.kt new file mode 100644 index 0000000..b4d5c32 --- /dev/null +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/RichTextStyleRule.kt @@ -0,0 +1,32 @@ +/// +/// RichTextStyleRule.kt +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +package com.margelo.nitro.nitrotext + +import androidx.annotation.Keep +import com.facebook.proguard.annotations.DoNotStrip +import com.margelo.nitro.core.* + + +/** + * Represents the JavaScript object/struct "RichTextStyleRule". + */ +@DoNotStrip +@Keep +data class RichTextStyleRule + @DoNotStrip + @Keep + constructor( + @DoNotStrip + @Keep + val selector: String, + @DoNotStrip + @Keep + val style: RichTextStyle + ) { + /* main constructor */ +} diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/views/HybridNitroTextManager.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/views/HybridNitroTextManager.kt index 7a5695c..89cd355 100644 --- a/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/views/HybridNitroTextManager.kt +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/views/HybridNitroTextManager.kt @@ -17,7 +17,7 @@ import com.nitrotext.* /** * Represents the React Native `ViewManager` for the "NitroText" Nitro HybridView. */ -open class HybridNitroTextManager: SimpleViewManager() { +class HybridNitroTextManager: SimpleViewManager() { private val views = hashMapOf() override fun getName(): String { diff --git a/nitrogen/generated/ios/NitroText-Swift-Cxx-Bridge.hpp b/nitrogen/generated/ios/NitroText-Swift-Cxx-Bridge.hpp index 2a1c879..7a5cf44 100644 --- a/nitrogen/generated/ios/NitroText-Swift-Cxx-Bridge.hpp +++ b/nitrogen/generated/ios/NitroText-Swift-Cxx-Bridge.hpp @@ -22,6 +22,12 @@ namespace margelo::nitro::nitrotext { struct Fragment; } namespace margelo::nitro::nitrotext { class HybridNitroTextSpec; } // Forward declaration of `LineBreakStrategyIOS` to properly resolve imports. namespace margelo::nitro::nitrotext { enum class LineBreakStrategyIOS; } +// Forward declaration of `NitroRenderer` to properly resolve imports. +namespace margelo::nitro::nitrotext { enum class NitroRenderer; } +// Forward declaration of `RichTextStyleRule` to properly resolve imports. +namespace margelo::nitro::nitrotext { struct RichTextStyleRule; } +// Forward declaration of `RichTextStyle` to properly resolve imports. +namespace margelo::nitro::nitrotext { struct RichTextStyle; } // Forward declaration of `TextAlign` to properly resolve imports. namespace margelo::nitro::nitrotext { enum class TextAlign; } // Forward declaration of `TextDecorationLine` to properly resolve imports. @@ -47,6 +53,9 @@ namespace NitroText { class HybridNitroTextSpec_cxx; } #include "Fragment.hpp" #include "HybridNitroTextSpec.hpp" #include "LineBreakStrategyIOS.hpp" +#include "NitroRenderer.hpp" +#include "RichTextStyle.hpp" +#include "RichTextStyleRule.hpp" #include "TextAlign.hpp" #include "TextDecorationLine.hpp" #include "TextDecorationStyle.hpp" @@ -211,6 +220,47 @@ namespace margelo::nitro::nitrotext::bridge::swift { return *optional; } + // pragma MARK: std::optional + /** + * Specialized version of `std::optional`. + */ + using std__optional_NitroRenderer_ = std::optional; + inline std::optional create_std__optional_NitroRenderer_(const NitroRenderer& value) noexcept { + return std::optional(value); + } + inline bool has_value_std__optional_NitroRenderer_(const std::optional& optional) noexcept { + return optional.has_value(); + } + inline NitroRenderer get_std__optional_NitroRenderer_(const std::optional& optional) noexcept { + return *optional; + } + + // pragma MARK: std::vector + /** + * Specialized version of `std::vector`. + */ + using std__vector_RichTextStyleRule_ = std::vector; + inline std::vector create_std__vector_RichTextStyleRule_(size_t size) noexcept { + std::vector vector; + vector.reserve(size); + return vector; + } + + // pragma MARK: std::optional> + /** + * Specialized version of `std::optional>`. + */ + using std__optional_std__vector_RichTextStyleRule__ = std::optional>; + inline std::optional> create_std__optional_std__vector_RichTextStyleRule__(const std::vector& value) noexcept { + return std::optional>(value); + } + inline bool has_value_std__optional_std__vector_RichTextStyleRule__(const std::optional>& optional) noexcept { + return optional.has_value(); + } + inline std::vector get_std__optional_std__vector_RichTextStyleRule__(const std::optional>& optional) noexcept { + return *optional; + } + // pragma MARK: std::optional /** * Specialized version of `std::optional`. diff --git a/nitrogen/generated/ios/NitroText-Swift-Cxx-Umbrella.hpp b/nitrogen/generated/ios/NitroText-Swift-Cxx-Umbrella.hpp index 4e7d601..f099eaa 100644 --- a/nitrogen/generated/ios/NitroText-Swift-Cxx-Umbrella.hpp +++ b/nitrogen/generated/ios/NitroText-Swift-Cxx-Umbrella.hpp @@ -22,6 +22,12 @@ namespace margelo::nitro::nitrotext { struct Fragment; } namespace margelo::nitro::nitrotext { class HybridNitroTextSpec; } // Forward declaration of `LineBreakStrategyIOS` to properly resolve imports. namespace margelo::nitro::nitrotext { enum class LineBreakStrategyIOS; } +// Forward declaration of `NitroRenderer` to properly resolve imports. +namespace margelo::nitro::nitrotext { enum class NitroRenderer; } +// Forward declaration of `RichTextStyleRule` to properly resolve imports. +namespace margelo::nitro::nitrotext { struct RichTextStyleRule; } +// Forward declaration of `RichTextStyle` to properly resolve imports. +namespace margelo::nitro::nitrotext { struct RichTextStyle; } // Forward declaration of `TextAlign` to properly resolve imports. namespace margelo::nitro::nitrotext { enum class TextAlign; } // Forward declaration of `TextDecorationLine` to properly resolve imports. @@ -43,6 +49,9 @@ namespace margelo::nitro::nitrotext { enum class TextTransform; } #include "Fragment.hpp" #include "HybridNitroTextSpec.hpp" #include "LineBreakStrategyIOS.hpp" +#include "NitroRenderer.hpp" +#include "RichTextStyle.hpp" +#include "RichTextStyleRule.hpp" #include "TextAlign.hpp" #include "TextDecorationLine.hpp" #include "TextDecorationStyle.hpp" diff --git a/nitrogen/generated/ios/c++/HybridNitroTextSpecSwift.hpp b/nitrogen/generated/ios/c++/HybridNitroTextSpecSwift.hpp index 975a1c0..dcb5c4f 100644 --- a/nitrogen/generated/ios/c++/HybridNitroTextSpecSwift.hpp +++ b/nitrogen/generated/ios/c++/HybridNitroTextSpecSwift.hpp @@ -26,6 +26,12 @@ namespace margelo::nitro::nitrotext { enum class TextTransform; } namespace margelo::nitro::nitrotext { enum class TextDecorationLine; } // Forward declaration of `TextDecorationStyle` to properly resolve imports. namespace margelo::nitro::nitrotext { enum class TextDecorationStyle; } +// Forward declaration of `NitroRenderer` to properly resolve imports. +namespace margelo::nitro::nitrotext { enum class NitroRenderer; } +// Forward declaration of `RichTextStyleRule` to properly resolve imports. +namespace margelo::nitro::nitrotext { struct RichTextStyleRule; } +// Forward declaration of `RichTextStyle` to properly resolve imports. +namespace margelo::nitro::nitrotext { struct RichTextStyle; } // Forward declaration of `EllipsizeMode` to properly resolve imports. namespace margelo::nitro::nitrotext { enum class EllipsizeMode; } // Forward declaration of `LineBreakStrategyIOS` to properly resolve imports. @@ -47,6 +53,9 @@ namespace margelo::nitro::nitrotext { struct TextLayout; } #include "TextTransform.hpp" #include "TextDecorationLine.hpp" #include "TextDecorationStyle.hpp" +#include "NitroRenderer.hpp" +#include "RichTextStyleRule.hpp" +#include "RichTextStyle.hpp" #include "EllipsizeMode.hpp" #include "LineBreakStrategyIOS.hpp" #include "DynamicTypeRamp.hpp" @@ -98,6 +107,20 @@ namespace margelo::nitro::nitrotext { inline void setFragments(const std::optional>& fragments) noexcept override { _swiftPart.setFragments(fragments); } + inline std::optional getRenderer() noexcept override { + auto __result = _swiftPart.getRenderer(); + return __result; + } + inline void setRenderer(std::optional renderer) noexcept override { + _swiftPart.setRenderer(renderer); + } + inline std::optional> getRichTextStyleRules() noexcept override { + auto __result = _swiftPart.getRichTextStyleRules(); + return __result; + } + inline void setRichTextStyleRules(const std::optional>& richTextStyleRules) noexcept override { + _swiftPart.setRichTextStyleRules(richTextStyleRules); + } inline std::optional getSelectable() noexcept override { auto __result = _swiftPart.getSelectable(); return __result; diff --git a/nitrogen/generated/ios/c++/views/HybridNitroTextComponent.mm b/nitrogen/generated/ios/c++/views/HybridNitroTextComponent.mm index 6a1ddc7..c8da4ae 100644 --- a/nitrogen/generated/ios/c++/views/HybridNitroTextComponent.mm +++ b/nitrogen/generated/ios/c++/views/HybridNitroTextComponent.mm @@ -76,6 +76,16 @@ - (void) updateProps:(const std::shared_ptr&)props swiftPart.setFragments(newViewProps.fragments.value); newViewProps.fragments.isDirty = false; } + // renderer: optional + if (newViewProps.renderer.isDirty) { + swiftPart.setRenderer(newViewProps.renderer.value); + newViewProps.renderer.isDirty = false; + } + // richTextStyleRules: optional + if (newViewProps.richTextStyleRules.isDirty) { + swiftPart.setRichTextStyleRules(newViewProps.richTextStyleRules.value); + newViewProps.richTextStyleRules.isDirty = false; + } // selectable: optional if (newViewProps.selectable.isDirty) { swiftPart.setSelectable(newViewProps.selectable.value); diff --git a/nitrogen/generated/ios/swift/HybridNitroTextSpec.swift b/nitrogen/generated/ios/swift/HybridNitroTextSpec.swift index 4ae8ffa..d2ed3fd 100644 --- a/nitrogen/generated/ios/swift/HybridNitroTextSpec.swift +++ b/nitrogen/generated/ios/swift/HybridNitroTextSpec.swift @@ -12,6 +12,8 @@ import NitroModules public protocol HybridNitroTextSpec_protocol: HybridObject, HybridView { // Properties var fragments: [Fragment]? { get set } + var renderer: NitroRenderer? { get set } + var richTextStyleRules: [RichTextStyleRule]? { get set } var selectable: Bool? { get set } var allowFontScaling: Bool? { get set } var ellipsizeMode: EllipsizeMode? { get set } diff --git a/nitrogen/generated/ios/swift/HybridNitroTextSpec_cxx.swift b/nitrogen/generated/ios/swift/HybridNitroTextSpec_cxx.swift index 9b994d1..229f83c 100644 --- a/nitrogen/generated/ios/swift/HybridNitroTextSpec_cxx.swift +++ b/nitrogen/generated/ios/swift/HybridNitroTextSpec_cxx.swift @@ -136,6 +136,53 @@ open class HybridNitroTextSpec_cxx { } } + public final var renderer: bridge.std__optional_NitroRenderer_ { + @inline(__always) + get { + return { () -> bridge.std__optional_NitroRenderer_ in + if let __unwrappedValue = self.__implementation.renderer { + return bridge.create_std__optional_NitroRenderer_(__unwrappedValue) + } else { + return .init() + } + }() + } + @inline(__always) + set { + self.__implementation.renderer = newValue.value + } + } + + public final var richTextStyleRules: bridge.std__optional_std__vector_RichTextStyleRule__ { + @inline(__always) + get { + return { () -> bridge.std__optional_std__vector_RichTextStyleRule__ in + if let __unwrappedValue = self.__implementation.richTextStyleRules { + return bridge.create_std__optional_std__vector_RichTextStyleRule__({ () -> bridge.std__vector_RichTextStyleRule_ in + var __vector = bridge.create_std__vector_RichTextStyleRule_(__unwrappedValue.count) + for __item in __unwrappedValue { + __vector.push_back(__item) + } + return __vector + }()) + } else { + return .init() + } + }() + } + @inline(__always) + set { + self.__implementation.richTextStyleRules = { () -> [RichTextStyleRule]? in + if bridge.has_value_std__optional_std__vector_RichTextStyleRule__(newValue) { + let __unwrapped = bridge.get_std__optional_std__vector_RichTextStyleRule__(newValue) + return __unwrapped.map({ __item in __item }) + } else { + return nil + } + }() + } + } + public final var selectable: bridge.std__optional_bool_ { @inline(__always) get { diff --git a/nitrogen/generated/ios/swift/NitroRenderer.swift b/nitrogen/generated/ios/swift/NitroRenderer.swift new file mode 100644 index 0000000..4028603 --- /dev/null +++ b/nitrogen/generated/ios/swift/NitroRenderer.swift @@ -0,0 +1,40 @@ +/// +/// NitroRenderer.swift +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +/** + * Represents the JS union `NitroRenderer`, backed by a C++ enum. + */ +public typealias NitroRenderer = margelo.nitro.nitrotext.NitroRenderer + +public extension NitroRenderer { + /** + * Get a NitroRenderer for the given String value, or + * return `nil` if the given value was invalid/unknown. + */ + init?(fromString string: String) { + switch string { + case "plaintext": + self = .plaintext + case "html": + self = .html + default: + return nil + } + } + + /** + * Get the String value this NitroRenderer represents. + */ + var stringValue: String { + switch self { + case .plaintext: + return "plaintext" + case .html: + return "html" + } + } +} diff --git a/nitrogen/generated/ios/swift/RichTextStyle.swift b/nitrogen/generated/ios/swift/RichTextStyle.swift new file mode 100644 index 0000000..ce4ce46 --- /dev/null +++ b/nitrogen/generated/ios/swift/RichTextStyle.swift @@ -0,0 +1,443 @@ +/// +/// RichTextStyle.swift +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +import NitroModules + +/** + * Represents an instance of `RichTextStyle`, backed by a C++ struct. + */ +public typealias RichTextStyle = margelo.nitro.nitrotext.RichTextStyle + +public extension RichTextStyle { + private typealias bridge = margelo.nitro.nitrotext.bridge.swift + + /** + * Create a new instance of `RichTextStyle`. + */ + init(fontColor: String?, fragmentBackgroundColor: String?, fontSize: Double?, fontWeight: FontWeight?, fontStyle: FontStyle?, fontFamily: String?, lineHeight: Double?, letterSpacing: Double?, textAlign: TextAlign?, textTransform: TextTransform?, textDecorationLine: TextDecorationLine?, textDecorationColor: String?, textDecorationStyle: TextDecorationStyle?, marginTop: Double?, marginBottom: Double?, marginLeft: Double?, marginRight: Double?) { + self.init({ () -> bridge.std__optional_std__string_ in + if let __unwrappedValue = fontColor { + return bridge.create_std__optional_std__string_(std.string(__unwrappedValue)) + } else { + return .init() + } + }(), { () -> bridge.std__optional_std__string_ in + if let __unwrappedValue = fragmentBackgroundColor { + return bridge.create_std__optional_std__string_(std.string(__unwrappedValue)) + } else { + return .init() + } + }(), { () -> bridge.std__optional_double_ in + if let __unwrappedValue = fontSize { + return bridge.create_std__optional_double_(__unwrappedValue) + } else { + return .init() + } + }(), { () -> bridge.std__optional_FontWeight_ in + if let __unwrappedValue = fontWeight { + return bridge.create_std__optional_FontWeight_(__unwrappedValue) + } else { + return .init() + } + }(), { () -> bridge.std__optional_FontStyle_ in + if let __unwrappedValue = fontStyle { + return bridge.create_std__optional_FontStyle_(__unwrappedValue) + } else { + return .init() + } + }(), { () -> bridge.std__optional_std__string_ in + if let __unwrappedValue = fontFamily { + return bridge.create_std__optional_std__string_(std.string(__unwrappedValue)) + } else { + return .init() + } + }(), { () -> bridge.std__optional_double_ in + if let __unwrappedValue = lineHeight { + return bridge.create_std__optional_double_(__unwrappedValue) + } else { + return .init() + } + }(), { () -> bridge.std__optional_double_ in + if let __unwrappedValue = letterSpacing { + return bridge.create_std__optional_double_(__unwrappedValue) + } else { + return .init() + } + }(), { () -> bridge.std__optional_TextAlign_ in + if let __unwrappedValue = textAlign { + return bridge.create_std__optional_TextAlign_(__unwrappedValue) + } else { + return .init() + } + }(), { () -> bridge.std__optional_TextTransform_ in + if let __unwrappedValue = textTransform { + return bridge.create_std__optional_TextTransform_(__unwrappedValue) + } else { + return .init() + } + }(), { () -> bridge.std__optional_TextDecorationLine_ in + if let __unwrappedValue = textDecorationLine { + return bridge.create_std__optional_TextDecorationLine_(__unwrappedValue) + } else { + return .init() + } + }(), { () -> bridge.std__optional_std__string_ in + if let __unwrappedValue = textDecorationColor { + return bridge.create_std__optional_std__string_(std.string(__unwrappedValue)) + } else { + return .init() + } + }(), { () -> bridge.std__optional_TextDecorationStyle_ in + if let __unwrappedValue = textDecorationStyle { + return bridge.create_std__optional_TextDecorationStyle_(__unwrappedValue) + } else { + return .init() + } + }(), { () -> bridge.std__optional_double_ in + if let __unwrappedValue = marginTop { + return bridge.create_std__optional_double_(__unwrappedValue) + } else { + return .init() + } + }(), { () -> bridge.std__optional_double_ in + if let __unwrappedValue = marginBottom { + return bridge.create_std__optional_double_(__unwrappedValue) + } else { + return .init() + } + }(), { () -> bridge.std__optional_double_ in + if let __unwrappedValue = marginLeft { + return bridge.create_std__optional_double_(__unwrappedValue) + } else { + return .init() + } + }(), { () -> bridge.std__optional_double_ in + if let __unwrappedValue = marginRight { + return bridge.create_std__optional_double_(__unwrappedValue) + } else { + return .init() + } + }()) + } + + var fontColor: String? { + @inline(__always) + get { + return { () -> String? in + if bridge.has_value_std__optional_std__string_(self.__fontColor) { + let __unwrapped = bridge.get_std__optional_std__string_(self.__fontColor) + return String(__unwrapped) + } else { + return nil + } + }() + } + @inline(__always) + set { + self.__fontColor = { () -> bridge.std__optional_std__string_ in + if let __unwrappedValue = newValue { + return bridge.create_std__optional_std__string_(std.string(__unwrappedValue)) + } else { + return .init() + } + }() + } + } + + var fragmentBackgroundColor: String? { + @inline(__always) + get { + return { () -> String? in + if bridge.has_value_std__optional_std__string_(self.__fragmentBackgroundColor) { + let __unwrapped = bridge.get_std__optional_std__string_(self.__fragmentBackgroundColor) + return String(__unwrapped) + } else { + return nil + } + }() + } + @inline(__always) + set { + self.__fragmentBackgroundColor = { () -> bridge.std__optional_std__string_ in + if let __unwrappedValue = newValue { + return bridge.create_std__optional_std__string_(std.string(__unwrappedValue)) + } else { + return .init() + } + }() + } + } + + var fontSize: Double? { + @inline(__always) + get { + return self.__fontSize.value + } + @inline(__always) + set { + self.__fontSize = { () -> bridge.std__optional_double_ in + if let __unwrappedValue = newValue { + return bridge.create_std__optional_double_(__unwrappedValue) + } else { + return .init() + } + }() + } + } + + var fontWeight: FontWeight? { + @inline(__always) + get { + return self.__fontWeight.value + } + @inline(__always) + set { + self.__fontWeight = { () -> bridge.std__optional_FontWeight_ in + if let __unwrappedValue = newValue { + return bridge.create_std__optional_FontWeight_(__unwrappedValue) + } else { + return .init() + } + }() + } + } + + var fontStyle: FontStyle? { + @inline(__always) + get { + return self.__fontStyle.value + } + @inline(__always) + set { + self.__fontStyle = { () -> bridge.std__optional_FontStyle_ in + if let __unwrappedValue = newValue { + return bridge.create_std__optional_FontStyle_(__unwrappedValue) + } else { + return .init() + } + }() + } + } + + var fontFamily: String? { + @inline(__always) + get { + return { () -> String? in + if bridge.has_value_std__optional_std__string_(self.__fontFamily) { + let __unwrapped = bridge.get_std__optional_std__string_(self.__fontFamily) + return String(__unwrapped) + } else { + return nil + } + }() + } + @inline(__always) + set { + self.__fontFamily = { () -> bridge.std__optional_std__string_ in + if let __unwrappedValue = newValue { + return bridge.create_std__optional_std__string_(std.string(__unwrappedValue)) + } else { + return .init() + } + }() + } + } + + var lineHeight: Double? { + @inline(__always) + get { + return self.__lineHeight.value + } + @inline(__always) + set { + self.__lineHeight = { () -> bridge.std__optional_double_ in + if let __unwrappedValue = newValue { + return bridge.create_std__optional_double_(__unwrappedValue) + } else { + return .init() + } + }() + } + } + + var letterSpacing: Double? { + @inline(__always) + get { + return self.__letterSpacing.value + } + @inline(__always) + set { + self.__letterSpacing = { () -> bridge.std__optional_double_ in + if let __unwrappedValue = newValue { + return bridge.create_std__optional_double_(__unwrappedValue) + } else { + return .init() + } + }() + } + } + + var textAlign: TextAlign? { + @inline(__always) + get { + return self.__textAlign.value + } + @inline(__always) + set { + self.__textAlign = { () -> bridge.std__optional_TextAlign_ in + if let __unwrappedValue = newValue { + return bridge.create_std__optional_TextAlign_(__unwrappedValue) + } else { + return .init() + } + }() + } + } + + var textTransform: TextTransform? { + @inline(__always) + get { + return self.__textTransform.value + } + @inline(__always) + set { + self.__textTransform = { () -> bridge.std__optional_TextTransform_ in + if let __unwrappedValue = newValue { + return bridge.create_std__optional_TextTransform_(__unwrappedValue) + } else { + return .init() + } + }() + } + } + + var textDecorationLine: TextDecorationLine? { + @inline(__always) + get { + return self.__textDecorationLine.value + } + @inline(__always) + set { + self.__textDecorationLine = { () -> bridge.std__optional_TextDecorationLine_ in + if let __unwrappedValue = newValue { + return bridge.create_std__optional_TextDecorationLine_(__unwrappedValue) + } else { + return .init() + } + }() + } + } + + var textDecorationColor: String? { + @inline(__always) + get { + return { () -> String? in + if bridge.has_value_std__optional_std__string_(self.__textDecorationColor) { + let __unwrapped = bridge.get_std__optional_std__string_(self.__textDecorationColor) + return String(__unwrapped) + } else { + return nil + } + }() + } + @inline(__always) + set { + self.__textDecorationColor = { () -> bridge.std__optional_std__string_ in + if let __unwrappedValue = newValue { + return bridge.create_std__optional_std__string_(std.string(__unwrappedValue)) + } else { + return .init() + } + }() + } + } + + var textDecorationStyle: TextDecorationStyle? { + @inline(__always) + get { + return self.__textDecorationStyle.value + } + @inline(__always) + set { + self.__textDecorationStyle = { () -> bridge.std__optional_TextDecorationStyle_ in + if let __unwrappedValue = newValue { + return bridge.create_std__optional_TextDecorationStyle_(__unwrappedValue) + } else { + return .init() + } + }() + } + } + + var marginTop: Double? { + @inline(__always) + get { + return self.__marginTop.value + } + @inline(__always) + set { + self.__marginTop = { () -> bridge.std__optional_double_ in + if let __unwrappedValue = newValue { + return bridge.create_std__optional_double_(__unwrappedValue) + } else { + return .init() + } + }() + } + } + + var marginBottom: Double? { + @inline(__always) + get { + return self.__marginBottom.value + } + @inline(__always) + set { + self.__marginBottom = { () -> bridge.std__optional_double_ in + if let __unwrappedValue = newValue { + return bridge.create_std__optional_double_(__unwrappedValue) + } else { + return .init() + } + }() + } + } + + var marginLeft: Double? { + @inline(__always) + get { + return self.__marginLeft.value + } + @inline(__always) + set { + self.__marginLeft = { () -> bridge.std__optional_double_ in + if let __unwrappedValue = newValue { + return bridge.create_std__optional_double_(__unwrappedValue) + } else { + return .init() + } + }() + } + } + + var marginRight: Double? { + @inline(__always) + get { + return self.__marginRight.value + } + @inline(__always) + set { + self.__marginRight = { () -> bridge.std__optional_double_ in + if let __unwrappedValue = newValue { + return bridge.create_std__optional_double_(__unwrappedValue) + } else { + return .init() + } + }() + } + } +} diff --git a/nitrogen/generated/ios/swift/RichTextStyleRule.swift b/nitrogen/generated/ios/swift/RichTextStyleRule.swift new file mode 100644 index 0000000..8c708e4 --- /dev/null +++ b/nitrogen/generated/ios/swift/RichTextStyleRule.swift @@ -0,0 +1,46 @@ +/// +/// RichTextStyleRule.swift +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +import NitroModules + +/** + * Represents an instance of `RichTextStyleRule`, backed by a C++ struct. + */ +public typealias RichTextStyleRule = margelo.nitro.nitrotext.RichTextStyleRule + +public extension RichTextStyleRule { + private typealias bridge = margelo.nitro.nitrotext.bridge.swift + + /** + * Create a new instance of `RichTextStyleRule`. + */ + init(selector: String, style: RichTextStyle) { + self.init(std.string(selector), style) + } + + var selector: String { + @inline(__always) + get { + return String(self.__selector) + } + @inline(__always) + set { + self.__selector = std.string(newValue) + } + } + + var style: RichTextStyle { + @inline(__always) + get { + return self.__style + } + @inline(__always) + set { + self.__style = newValue + } + } +} diff --git a/nitrogen/generated/shared/c++/HybridNitroTextSpec.cpp b/nitrogen/generated/shared/c++/HybridNitroTextSpec.cpp index c36a8f7..ea4bb37 100644 --- a/nitrogen/generated/shared/c++/HybridNitroTextSpec.cpp +++ b/nitrogen/generated/shared/c++/HybridNitroTextSpec.cpp @@ -16,6 +16,10 @@ namespace margelo::nitro::nitrotext { registerHybrids(this, [](Prototype& prototype) { prototype.registerHybridGetter("fragments", &HybridNitroTextSpec::getFragments); prototype.registerHybridSetter("fragments", &HybridNitroTextSpec::setFragments); + prototype.registerHybridGetter("renderer", &HybridNitroTextSpec::getRenderer); + prototype.registerHybridSetter("renderer", &HybridNitroTextSpec::setRenderer); + prototype.registerHybridGetter("richTextStyleRules", &HybridNitroTextSpec::getRichTextStyleRules); + prototype.registerHybridSetter("richTextStyleRules", &HybridNitroTextSpec::setRichTextStyleRules); prototype.registerHybridGetter("selectable", &HybridNitroTextSpec::getSelectable); prototype.registerHybridSetter("selectable", &HybridNitroTextSpec::setSelectable); prototype.registerHybridGetter("allowFontScaling", &HybridNitroTextSpec::getAllowFontScaling); diff --git a/nitrogen/generated/shared/c++/HybridNitroTextSpec.hpp b/nitrogen/generated/shared/c++/HybridNitroTextSpec.hpp index 88b4c36..f5f26f9 100644 --- a/nitrogen/generated/shared/c++/HybridNitroTextSpec.hpp +++ b/nitrogen/generated/shared/c++/HybridNitroTextSpec.hpp @@ -15,6 +15,10 @@ // Forward declaration of `Fragment` to properly resolve imports. namespace margelo::nitro::nitrotext { struct Fragment; } +// Forward declaration of `NitroRenderer` to properly resolve imports. +namespace margelo::nitro::nitrotext { enum class NitroRenderer; } +// Forward declaration of `RichTextStyleRule` to properly resolve imports. +namespace margelo::nitro::nitrotext { struct RichTextStyleRule; } // Forward declaration of `EllipsizeMode` to properly resolve imports. namespace margelo::nitro::nitrotext { enum class EllipsizeMode; } // Forward declaration of `LineBreakStrategyIOS` to properly resolve imports. @@ -39,6 +43,8 @@ namespace margelo::nitro::nitrotext { enum class TextDecorationStyle; } #include "Fragment.hpp" #include #include +#include "NitroRenderer.hpp" +#include "RichTextStyleRule.hpp" #include "EllipsizeMode.hpp" #include "LineBreakStrategyIOS.hpp" #include "DynamicTypeRamp.hpp" @@ -81,6 +87,10 @@ namespace margelo::nitro::nitrotext { // Properties virtual std::optional> getFragments() = 0; virtual void setFragments(const std::optional>& fragments) = 0; + virtual std::optional getRenderer() = 0; + virtual void setRenderer(std::optional renderer) = 0; + virtual std::optional> getRichTextStyleRules() = 0; + virtual void setRichTextStyleRules(const std::optional>& richTextStyleRules) = 0; virtual std::optional getSelectable() = 0; virtual void setSelectable(std::optional selectable) = 0; virtual std::optional getAllowFontScaling() = 0; diff --git a/nitrogen/generated/shared/c++/NitroRenderer.hpp b/nitrogen/generated/shared/c++/NitroRenderer.hpp new file mode 100644 index 0000000..49305e5 --- /dev/null +++ b/nitrogen/generated/shared/c++/NitroRenderer.hpp @@ -0,0 +1,76 @@ +/// +/// NitroRenderer.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#pragma once + +#if __has_include() +#include +#else +#error NitroModules cannot be found! Are you sure you installed NitroModules properly? +#endif +#if __has_include() +#include +#else +#error NitroModules cannot be found! Are you sure you installed NitroModules properly? +#endif +#if __has_include() +#include +#else +#error NitroModules cannot be found! Are you sure you installed NitroModules properly? +#endif + +namespace margelo::nitro::nitrotext { + + /** + * An enum which can be represented as a JavaScript union (NitroRenderer). + */ + enum class NitroRenderer { + PLAINTEXT SWIFT_NAME(plaintext) = 0, + HTML SWIFT_NAME(html) = 1, + } CLOSED_ENUM; + +} // namespace margelo::nitro::nitrotext + +namespace margelo::nitro { + + // C++ NitroRenderer <> JS NitroRenderer (union) + template <> + struct JSIConverter final { + static inline margelo::nitro::nitrotext::NitroRenderer fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) { + std::string unionValue = JSIConverter::fromJSI(runtime, arg); + switch (hashString(unionValue.c_str(), unionValue.size())) { + case hashString("plaintext"): return margelo::nitro::nitrotext::NitroRenderer::PLAINTEXT; + case hashString("html"): return margelo::nitro::nitrotext::NitroRenderer::HTML; + default: [[unlikely]] + throw std::invalid_argument("Cannot convert \"" + unionValue + "\" to enum NitroRenderer - invalid value!"); + } + } + static inline jsi::Value toJSI(jsi::Runtime& runtime, margelo::nitro::nitrotext::NitroRenderer arg) { + switch (arg) { + case margelo::nitro::nitrotext::NitroRenderer::PLAINTEXT: return JSIConverter::toJSI(runtime, "plaintext"); + case margelo::nitro::nitrotext::NitroRenderer::HTML: return JSIConverter::toJSI(runtime, "html"); + default: [[unlikely]] + throw std::invalid_argument("Cannot convert NitroRenderer to JS - invalid value: " + + std::to_string(static_cast(arg)) + "!"); + } + } + static inline bool canConvert(jsi::Runtime& runtime, const jsi::Value& value) { + if (!value.isString()) { + return false; + } + std::string unionValue = JSIConverter::fromJSI(runtime, value); + switch (hashString(unionValue.c_str(), unionValue.size())) { + case hashString("plaintext"): + case hashString("html"): + return true; + default: + return false; + } + } + }; + +} // namespace margelo::nitro diff --git a/nitrogen/generated/shared/c++/RichTextStyle.hpp b/nitrogen/generated/shared/c++/RichTextStyle.hpp new file mode 100644 index 0000000..7933df8 --- /dev/null +++ b/nitrogen/generated/shared/c++/RichTextStyle.hpp @@ -0,0 +1,149 @@ +/// +/// RichTextStyle.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#pragma once + +#if __has_include() +#include +#else +#error NitroModules cannot be found! Are you sure you installed NitroModules properly? +#endif +#if __has_include() +#include +#else +#error NitroModules cannot be found! Are you sure you installed NitroModules properly? +#endif + +// Forward declaration of `FontWeight` to properly resolve imports. +namespace margelo::nitro::nitrotext { enum class FontWeight; } +// Forward declaration of `FontStyle` to properly resolve imports. +namespace margelo::nitro::nitrotext { enum class FontStyle; } +// Forward declaration of `TextAlign` to properly resolve imports. +namespace margelo::nitro::nitrotext { enum class TextAlign; } +// Forward declaration of `TextTransform` to properly resolve imports. +namespace margelo::nitro::nitrotext { enum class TextTransform; } +// Forward declaration of `TextDecorationLine` to properly resolve imports. +namespace margelo::nitro::nitrotext { enum class TextDecorationLine; } +// Forward declaration of `TextDecorationStyle` to properly resolve imports. +namespace margelo::nitro::nitrotext { enum class TextDecorationStyle; } + +#include +#include +#include "FontWeight.hpp" +#include "FontStyle.hpp" +#include "TextAlign.hpp" +#include "TextTransform.hpp" +#include "TextDecorationLine.hpp" +#include "TextDecorationStyle.hpp" + +namespace margelo::nitro::nitrotext { + + /** + * A struct which can be represented as a JavaScript object (RichTextStyle). + */ + struct RichTextStyle { + public: + std::optional fontColor SWIFT_PRIVATE; + std::optional fragmentBackgroundColor SWIFT_PRIVATE; + std::optional fontSize SWIFT_PRIVATE; + std::optional fontWeight SWIFT_PRIVATE; + std::optional fontStyle SWIFT_PRIVATE; + std::optional fontFamily SWIFT_PRIVATE; + std::optional lineHeight SWIFT_PRIVATE; + std::optional letterSpacing SWIFT_PRIVATE; + std::optional textAlign SWIFT_PRIVATE; + std::optional textTransform SWIFT_PRIVATE; + std::optional textDecorationLine SWIFT_PRIVATE; + std::optional textDecorationColor SWIFT_PRIVATE; + std::optional textDecorationStyle SWIFT_PRIVATE; + std::optional marginTop SWIFT_PRIVATE; + std::optional marginBottom SWIFT_PRIVATE; + std::optional marginLeft SWIFT_PRIVATE; + std::optional marginRight SWIFT_PRIVATE; + + public: + RichTextStyle() = default; + explicit RichTextStyle(std::optional fontColor, std::optional fragmentBackgroundColor, std::optional fontSize, std::optional fontWeight, std::optional fontStyle, std::optional fontFamily, std::optional lineHeight, std::optional letterSpacing, std::optional textAlign, std::optional textTransform, std::optional textDecorationLine, std::optional textDecorationColor, std::optional textDecorationStyle, std::optional marginTop, std::optional marginBottom, std::optional marginLeft, std::optional marginRight): fontColor(fontColor), fragmentBackgroundColor(fragmentBackgroundColor), fontSize(fontSize), fontWeight(fontWeight), fontStyle(fontStyle), fontFamily(fontFamily), lineHeight(lineHeight), letterSpacing(letterSpacing), textAlign(textAlign), textTransform(textTransform), textDecorationLine(textDecorationLine), textDecorationColor(textDecorationColor), textDecorationStyle(textDecorationStyle), marginTop(marginTop), marginBottom(marginBottom), marginLeft(marginLeft), marginRight(marginRight) {} + }; + +} // namespace margelo::nitro::nitrotext + +namespace margelo::nitro { + + // C++ RichTextStyle <> JS RichTextStyle (object) + template <> + struct JSIConverter final { + static inline margelo::nitro::nitrotext::RichTextStyle fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) { + jsi::Object obj = arg.asObject(runtime); + return margelo::nitro::nitrotext::RichTextStyle( + JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, "fontColor")), + JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, "fragmentBackgroundColor")), + JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, "fontSize")), + JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, "fontWeight")), + JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, "fontStyle")), + JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, "fontFamily")), + JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, "lineHeight")), + JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, "letterSpacing")), + JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, "textAlign")), + JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, "textTransform")), + JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, "textDecorationLine")), + JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, "textDecorationColor")), + JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, "textDecorationStyle")), + JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, "marginTop")), + JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, "marginBottom")), + JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, "marginLeft")), + JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, "marginRight")) + ); + } + static inline jsi::Value toJSI(jsi::Runtime& runtime, const margelo::nitro::nitrotext::RichTextStyle& arg) { + jsi::Object obj(runtime); + obj.setProperty(runtime, "fontColor", JSIConverter>::toJSI(runtime, arg.fontColor)); + obj.setProperty(runtime, "fragmentBackgroundColor", JSIConverter>::toJSI(runtime, arg.fragmentBackgroundColor)); + obj.setProperty(runtime, "fontSize", JSIConverter>::toJSI(runtime, arg.fontSize)); + obj.setProperty(runtime, "fontWeight", JSIConverter>::toJSI(runtime, arg.fontWeight)); + obj.setProperty(runtime, "fontStyle", JSIConverter>::toJSI(runtime, arg.fontStyle)); + obj.setProperty(runtime, "fontFamily", JSIConverter>::toJSI(runtime, arg.fontFamily)); + obj.setProperty(runtime, "lineHeight", JSIConverter>::toJSI(runtime, arg.lineHeight)); + obj.setProperty(runtime, "letterSpacing", JSIConverter>::toJSI(runtime, arg.letterSpacing)); + obj.setProperty(runtime, "textAlign", JSIConverter>::toJSI(runtime, arg.textAlign)); + obj.setProperty(runtime, "textTransform", JSIConverter>::toJSI(runtime, arg.textTransform)); + obj.setProperty(runtime, "textDecorationLine", JSIConverter>::toJSI(runtime, arg.textDecorationLine)); + obj.setProperty(runtime, "textDecorationColor", JSIConverter>::toJSI(runtime, arg.textDecorationColor)); + obj.setProperty(runtime, "textDecorationStyle", JSIConverter>::toJSI(runtime, arg.textDecorationStyle)); + obj.setProperty(runtime, "marginTop", JSIConverter>::toJSI(runtime, arg.marginTop)); + obj.setProperty(runtime, "marginBottom", JSIConverter>::toJSI(runtime, arg.marginBottom)); + obj.setProperty(runtime, "marginLeft", JSIConverter>::toJSI(runtime, arg.marginLeft)); + obj.setProperty(runtime, "marginRight", JSIConverter>::toJSI(runtime, arg.marginRight)); + return obj; + } + static inline bool canConvert(jsi::Runtime& runtime, const jsi::Value& value) { + if (!value.isObject()) { + return false; + } + jsi::Object obj = value.getObject(runtime); + if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, "fontColor"))) return false; + if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, "fragmentBackgroundColor"))) return false; + if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, "fontSize"))) return false; + if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, "fontWeight"))) return false; + if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, "fontStyle"))) return false; + if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, "fontFamily"))) return false; + if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, "lineHeight"))) return false; + if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, "letterSpacing"))) return false; + if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, "textAlign"))) return false; + if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, "textTransform"))) return false; + if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, "textDecorationLine"))) return false; + if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, "textDecorationColor"))) return false; + if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, "textDecorationStyle"))) return false; + if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, "marginTop"))) return false; + if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, "marginBottom"))) return false; + if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, "marginLeft"))) return false; + if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, "marginRight"))) return false; + return true; + } + }; + +} // namespace margelo::nitro diff --git a/nitrogen/generated/shared/c++/RichTextStyleRule.hpp b/nitrogen/generated/shared/c++/RichTextStyleRule.hpp new file mode 100644 index 0000000..7d5963c --- /dev/null +++ b/nitrogen/generated/shared/c++/RichTextStyleRule.hpp @@ -0,0 +1,73 @@ +/// +/// RichTextStyleRule.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#pragma once + +#if __has_include() +#include +#else +#error NitroModules cannot be found! Are you sure you installed NitroModules properly? +#endif +#if __has_include() +#include +#else +#error NitroModules cannot be found! Are you sure you installed NitroModules properly? +#endif + +// Forward declaration of `RichTextStyle` to properly resolve imports. +namespace margelo::nitro::nitrotext { struct RichTextStyle; } + +#include +#include "RichTextStyle.hpp" + +namespace margelo::nitro::nitrotext { + + /** + * A struct which can be represented as a JavaScript object (RichTextStyleRule). + */ + struct RichTextStyleRule { + public: + std::string selector SWIFT_PRIVATE; + RichTextStyle style SWIFT_PRIVATE; + + public: + RichTextStyleRule() = default; + explicit RichTextStyleRule(std::string selector, RichTextStyle style): selector(selector), style(style) {} + }; + +} // namespace margelo::nitro::nitrotext + +namespace margelo::nitro { + + // C++ RichTextStyleRule <> JS RichTextStyleRule (object) + template <> + struct JSIConverter final { + static inline margelo::nitro::nitrotext::RichTextStyleRule fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) { + jsi::Object obj = arg.asObject(runtime); + return margelo::nitro::nitrotext::RichTextStyleRule( + JSIConverter::fromJSI(runtime, obj.getProperty(runtime, "selector")), + JSIConverter::fromJSI(runtime, obj.getProperty(runtime, "style")) + ); + } + static inline jsi::Value toJSI(jsi::Runtime& runtime, const margelo::nitro::nitrotext::RichTextStyleRule& arg) { + jsi::Object obj(runtime); + obj.setProperty(runtime, "selector", JSIConverter::toJSI(runtime, arg.selector)); + obj.setProperty(runtime, "style", JSIConverter::toJSI(runtime, arg.style)); + return obj; + } + static inline bool canConvert(jsi::Runtime& runtime, const jsi::Value& value) { + if (!value.isObject()) { + return false; + } + jsi::Object obj = value.getObject(runtime); + if (!JSIConverter::canConvert(runtime, obj.getProperty(runtime, "selector"))) return false; + if (!JSIConverter::canConvert(runtime, obj.getProperty(runtime, "style"))) return false; + return true; + } + }; + +} // namespace margelo::nitro diff --git a/nitrogen/generated/shared/c++/views/HybridNitroTextComponent.cpp b/nitrogen/generated/shared/c++/views/HybridNitroTextComponent.cpp index 0ddfe96..d2fdb30 100644 --- a/nitrogen/generated/shared/c++/views/HybridNitroTextComponent.cpp +++ b/nitrogen/generated/shared/c++/views/HybridNitroTextComponent.cpp @@ -35,6 +35,26 @@ namespace margelo::nitro::nitrotext::views { throw std::runtime_error(std::string("NitroText.fragments: ") + exc.what()); } }()), + renderer([&]() -> CachedProp> { + try { + const react::RawValue* rawValue = rawProps.at("renderer", nullptr, nullptr); + if (rawValue == nullptr) return sourceProps.renderer; + const auto& [runtime, value] = (std::pair)*rawValue; + return CachedProp>::fromRawValue(*runtime, value, sourceProps.renderer); + } catch (const std::exception& exc) { + throw std::runtime_error(std::string("NitroText.renderer: ") + exc.what()); + } + }()), + richTextStyleRules([&]() -> CachedProp>> { + try { + const react::RawValue* rawValue = rawProps.at("richTextStyleRules", nullptr, nullptr); + if (rawValue == nullptr) return sourceProps.richTextStyleRules; + const auto& [runtime, value] = (std::pair)*rawValue; + return CachedProp>>::fromRawValue(*runtime, value, sourceProps.richTextStyleRules); + } catch (const std::exception& exc) { + throw std::runtime_error(std::string("NitroText.richTextStyleRules: ") + exc.what()); + } + }()), selectable([&]() -> CachedProp> { try { const react::RawValue* rawValue = rawProps.at("selectable", nullptr, nullptr); @@ -329,6 +349,8 @@ namespace margelo::nitro::nitrotext::views { HybridNitroTextProps::HybridNitroTextProps(const HybridNitroTextProps& other): react::ViewProps(), fragments(other.fragments), + renderer(other.renderer), + richTextStyleRules(other.richTextStyleRules), selectable(other.selectable), allowFontScaling(other.allowFontScaling), ellipsizeMode(other.ellipsizeMode), @@ -362,6 +384,8 @@ namespace margelo::nitro::nitrotext::views { bool HybridNitroTextProps::filterObjectKeys(const std::string& propName) { switch (hashString(propName)) { case hashString("fragments"): return true; + case hashString("renderer"): return true; + case hashString("richTextStyleRules"): return true; case hashString("selectable"): return true; case hashString("allowFontScaling"): return true; case hashString("ellipsizeMode"): return true; diff --git a/nitrogen/generated/shared/c++/views/HybridNitroTextComponent.hpp b/nitrogen/generated/shared/c++/views/HybridNitroTextComponent.hpp index d7431c9..c32f06b 100644 --- a/nitrogen/generated/shared/c++/views/HybridNitroTextComponent.hpp +++ b/nitrogen/generated/shared/c++/views/HybridNitroTextComponent.hpp @@ -19,6 +19,8 @@ #include "Fragment.hpp" #include #include +#include "NitroRenderer.hpp" +#include "RichTextStyleRule.hpp" #include "EllipsizeMode.hpp" #include "LineBreakStrategyIOS.hpp" #include "DynamicTypeRamp.hpp" @@ -56,6 +58,8 @@ namespace margelo::nitro::nitrotext::views { public: CachedProp>> fragments; + CachedProp> renderer; + CachedProp>> richTextStyleRules; CachedProp> selectable; CachedProp> allowFontScaling; CachedProp> ellipsizeMode; diff --git a/nitrogen/generated/shared/json/NitroTextConfig.json b/nitrogen/generated/shared/json/NitroTextConfig.json index 6a08601..5dbafb9 100644 --- a/nitrogen/generated/shared/json/NitroTextConfig.json +++ b/nitrogen/generated/shared/json/NitroTextConfig.json @@ -5,6 +5,8 @@ "directEventTypes": {}, "validAttributes": { "fragments": true, + "renderer": true, + "richTextStyleRules": true, "selectable": true, "allowFontScaling": true, "ellipsizeMode": true, diff --git a/src/nitro-text.tsx b/src/nitro-text.tsx index e462713..2c40b8f 100644 --- a/src/nitro-text.tsx +++ b/src/nitro-text.tsx @@ -5,6 +5,8 @@ import { type TextLayoutEvent, type TextProps, unstable_TextAncestorContext, + type StyleProp, + type TextStyle, } from 'react-native' import { @@ -14,7 +16,8 @@ import { } from 'react-native-nitro-modules' import NitroTextConfig from '../nitrogen/generated/shared/json/NitroTextConfig.json' import type { NitroTextMethods, NitroTextProps } from './specs/nitro-text.nitro' -import { flattenChildrenToFragments, styleToFragment } from './utils' +import { buildRichTextStyleRules, flattenChildrenToFragments, styleToFragment } from './utils' +import type { NitroRenderer, RichTextStyleRule } from './types' export type NitroTextRef = HybridRef @@ -23,11 +26,11 @@ const NitroTextView = getHostComponent( () => NitroTextConfig ) -type NitroTextPropsWithEvents = Pick< - NitroTextProps, - 'onTextLayout' | 'onPress' | 'onPressIn' | 'onPressOut' -> & - Omit +type NitroTextPropsWithEvents = + Pick & + Omit & { + renderStyles?: Record> + } let TextAncestorContext = unstable_TextAncestorContext if ( @@ -49,17 +52,42 @@ export const NitroText = (props: NitroTextPropsWithEvents) => { onPressIn, onPressOut, onLongPress, + renderer = 'plaintext', + renderStyles, ...rest } = props - const isSimpleText = + const htmlText = React.useMemo(() => { + if (renderer !== 'html') return null + if (typeof children === 'string' || typeof children === 'number') { + return String(children) + } + const array = React.Children.toArray(children) + if (array.length === 0) return null + if (array.every((child) => typeof child === 'string' || typeof child === 'number')) { + return array.map((child) => String(child)).join('') + } + return null + }, [children, renderer]) + + const plainChildText = typeof children === 'string' || typeof children === 'number' + ? String(children) + : null + + const effectiveText = htmlText ?? plainChildText + const isSimpleText = effectiveText != null const fragments = React.useMemo(() => { if (isSimpleText) return [] return flattenChildrenToFragments(children, style as any) }, [children, style, isSimpleText]) + const richTextStyleRules: RichTextStyleRule[] | undefined = React.useMemo(() => { + if (renderer !== 'html') return undefined + return buildRichTextStyleRules(renderStyles) + }, [renderStyles, renderer]) + if (isInsideRNText) { const onRNTextLayout = useCallback( (e: TextLayoutEvent) => { @@ -94,7 +122,7 @@ export const NitroText = (props: NitroTextPropsWithEvents) => { selectable={selectable} fontFamily={topStyles.fontFamily} selectionColor={selectionColor as string} - text={String(children)} + text={effectiveText ?? ''} style={style} fontColor={topStyles.fontColor} textAlign={topStyles.textAlign} @@ -107,6 +135,8 @@ export const NitroText = (props: NitroTextPropsWithEvents) => { textDecorationLine={topStyles.textDecorationLine} textDecorationColor={topStyles.textDecorationColor} textDecorationStyle={topStyles.textDecorationStyle} + renderer={renderer as NitroRenderer} + richTextStyleRules={richTextStyleRules} onTextLayout={callback(onTextLayout)} onPress={callback(onPress)} onPressIn={callback(onPressIn)} @@ -134,6 +164,8 @@ export const NitroText = (props: NitroTextPropsWithEvents) => { textDecorationLine={topStyles.textDecorationLine} textDecorationColor={topStyles.textDecorationColor} textDecorationStyle={topStyles.textDecorationStyle} + renderer={renderer as NitroRenderer} + richTextStyleRules={richTextStyleRules} onTextLayout={callback(onTextLayout)} onPress={callback(onPress)} onPressIn={callback(onPressIn)} diff --git a/src/specs/nitro-text.nitro.ts b/src/specs/nitro-text.nitro.ts index 84178a3..44dc717 100755 --- a/src/specs/nitro-text.nitro.ts +++ b/src/specs/nitro-text.nitro.ts @@ -8,6 +8,8 @@ import type { EllipsizeMode, Fragment, LineBreakStrategyIOS, + NitroRenderer, + RichTextStyleRule, TextLayoutEvent, } from '../types' @@ -17,6 +19,16 @@ export interface NitroTextProps extends HybridViewProps, Fragment { */ fragments?: Fragment[] + /** + * Selects which renderer to use for the incoming `text`. Defaults to `plaintext`. + */ + renderer?: NitroRenderer + + /** + * Rich text style rules that can target HTML (or future markdown) elements by selector. + */ + richTextStyleRules?: RichTextStyleRule[] + /** * Selectable text. */ diff --git a/src/types.ts b/src/types.ts index 186fb30..4aadd03 100644 --- a/src/types.ts +++ b/src/types.ts @@ -118,3 +118,30 @@ export type Fragment = { */ textDecorationStyle?: TextDecorationStyle } + +export type NitroRenderer = 'plaintext' | 'html' + +export type RichTextStyle = { + fontColor?: string + fragmentBackgroundColor?: string + fontSize?: number + fontWeight?: FontWeight + fontStyle?: FontStyle + fontFamily?: string + lineHeight?: number + letterSpacing?: number + textAlign?: TextAlign + textTransform?: TextTransform + textDecorationLine?: TextDecorationLine + textDecorationColor?: string + textDecorationStyle?: TextDecorationStyle + marginTop?: number + marginBottom?: number + marginLeft?: number + marginRight?: number +} + +export type RichTextStyleRule = { + selector: string + style: RichTextStyle +} diff --git a/src/utils.ts b/src/utils.ts index 607f849..5339504 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,6 +1,6 @@ import React from "react"; import { StyleSheet, type StyleProp, type TextStyle } from "react-native"; -import type { Fragment } from "./types"; +import type { Fragment, RichTextStyle, RichTextStyleRule } from "./types"; export function normalizeWeight( w?: TextStyle['fontWeight'] @@ -153,3 +153,29 @@ export function flattenChildrenToFragments( flattenInto(out, children, parentStyle, fragmentConfig, {}); return out; } + +function styleToRichTextStyle(style: StyleProp | undefined): RichTextStyle { + const flat = StyleSheet.flatten(style) || {}; + const fragmentAttrs = styleToFragment(flat); + return { + ...fragmentAttrs, + marginTop: flat.marginTop as number | undefined, + marginBottom: flat.marginBottom as number | undefined, + marginLeft: flat.marginLeft as number | undefined, + marginRight: flat.marginRight as number | undefined, + }; +} + +export function buildRichTextStyleRules( + styles?: Record> +): RichTextStyleRule[] | undefined { + if (!styles) return undefined; + const entries = Object.entries(styles); + if (entries.length === 0) return undefined; + const rules: RichTextStyleRule[] = []; + for (const [selector, style] of entries) { + const normalized = styleToRichTextStyle(style); + rules.push({ selector, style: normalized }); + } + return rules; +} From 369ad10ff299ba329ce275af78e7c5fb1b720607 Mon Sep 17 00:00:00 2001 From: Patrick Kabwe Date: Thu, 2 Oct 2025 05:20:38 +0200 Subject: [PATCH 05/12] feat: add android support (#49) * feat(android): add NitroText HybridView implementation - add Android Nitro module with Kotlin view, Fabric state updater, and JNI bridge - share component descriptor/state logic so Fabric can hydrate props on Android - update TypeScript entrypoint, codegen pipeline, and example app for Android support * feat: add support for `onTextLayout` * feat: implement fragment background color support in NitroText --- android/CMakeLists.txt | 43 ++ android/build.gradle | 148 +++++++ android/gradle.properties | 5 + android/src/main/AndroidManifest.xml | 2 + .../main/cpp/NitroTextRegisterProvider.cpp | 25 ++ .../main/cpp/NitroTextRegisterProvider.hpp | 13 + android/src/main/cpp/cpp-adapter.cpp | 8 + .../java/com/nitrotext/HybridNitroText.kt | 178 ++++++++ .../java/com/nitrotext/NitroLineHeightSpan.kt | 29 ++ .../main/java/com/nitrotext/NitroTextImpl.kt | 301 +++++++++++++ .../java/com/nitrotext/NitroTextPackage.kt | 27 ++ .../main/java/com/nitrotext/NitroTextView.kt | 133 ++++++ .../com/nitrotext/NitroTextViewManager.kt | 48 ++ .../NitroTextComponentDescriptor.cpp | 10 +- cpp/NitroTextComponentDescriptor.hpp | 30 ++ cpp/NitroTextShadowNode.cpp | 22 +- cpp/NitroTextShadowNode.hpp | 4 + example/App.tsx | 30 +- example/android/build.gradle | 2 +- example/ios/Podfile.lock | 4 +- ...ybridNitroTextComponent+ShadowOverride.mm} | 5 +- ios/NitroTextComponentDescriptor.hpp | 31 -- ios/NitroTextImpl.swift | 5 - nitro.json | 3 +- .../android/NitroText+autolinking.cmake | 83 ++++ .../android/NitroText+autolinking.gradle | 27 ++ .../generated/android/NitroTextOnLoad.cpp | 50 +++ .../generated/android/NitroTextOnLoad.hpp | 25 ++ .../android/c++/JDynamicTypeRamp.hpp | 86 ++++ .../generated/android/c++/JEllipsizeMode.hpp | 65 +++ nitrogen/generated/android/c++/JFontStyle.hpp | 62 +++ .../generated/android/c++/JFontWeight.hpp | 89 ++++ nitrogen/generated/android/c++/JFragment.hpp | 122 ++++++ nitrogen/generated/android/c++/JFunc_void.hpp | 74 ++++ .../c++/JFunc_void_TextLayoutEvent.hpp | 80 ++++ .../android/c++/JHybridNitroTextSpec.cpp | 411 ++++++++++++++++++ .../android/c++/JHybridNitroTextSpec.hpp | 121 ++++++ .../android/c++/JLineBreakStrategyIOS.hpp | 65 +++ nitrogen/generated/android/c++/JTextAlign.hpp | 68 +++ .../android/c++/JTextDecorationLine.hpp | 65 +++ .../android/c++/JTextDecorationStyle.hpp | 65 +++ .../generated/android/c++/JTextLayout.hpp | 85 ++++ .../android/c++/JTextLayoutEvent.hpp | 73 ++++ .../generated/android/c++/JTextTransform.hpp | 65 +++ .../views/JHybridNitroTextStateUpdater.cpp | 168 +++++++ .../views/JHybridNitroTextStateUpdater.hpp | 49 +++ .../nitro/nitrotext/DynamicTypeRamp.kt | 30 ++ .../margelo/nitro/nitrotext/EllipsizeMode.kt | 23 + .../com/margelo/nitro/nitrotext/FontStyle.kt | 22 + .../com/margelo/nitro/nitrotext/FontWeight.kt | 31 ++ .../com/margelo/nitro/nitrotext/Fragment.kt | 71 +++ .../com/margelo/nitro/nitrotext/Func_void.kt | 81 ++++ .../nitrotext/Func_void_TextLayoutEvent.kt | 81 ++++ .../nitro/nitrotext/HybridNitroTextSpec.kt | 255 +++++++++++ .../nitro/nitrotext/LineBreakStrategyIOS.kt | 23 + .../nitro/nitrotext/NitroTextOnLoad.kt | 35 ++ .../com/margelo/nitro/nitrotext/TextAlign.kt | 24 + .../nitro/nitrotext/TextDecorationLine.kt | 23 + .../nitro/nitrotext/TextDecorationStyle.kt | 23 + .../com/margelo/nitro/nitrotext/TextLayout.kt | 53 +++ .../nitro/nitrotext/TextLayoutEvent.kt | 29 ++ .../margelo/nitro/nitrotext/TextTransform.kt | 23 + .../nitrotext/views/HybridNitroTextManager.kt | 50 +++ .../views/HybridNitroTextStateUpdater.kt | 23 + .../c++/views/HybridNitroTextComponent.hpp | 38 -- package.json | 4 +- post-script.js | 32 ++ src/nitro-text.tsx | 6 +- src/specs/nitro-text.nitro.ts | 2 +- 69 files changed, 3981 insertions(+), 105 deletions(-) create mode 100644 android/CMakeLists.txt create mode 100644 android/build.gradle create mode 100644 android/gradle.properties create mode 100644 android/src/main/AndroidManifest.xml create mode 100644 android/src/main/cpp/NitroTextRegisterProvider.cpp create mode 100644 android/src/main/cpp/NitroTextRegisterProvider.hpp create mode 100644 android/src/main/cpp/cpp-adapter.cpp create mode 100755 android/src/main/java/com/nitrotext/HybridNitroText.kt create mode 100644 android/src/main/java/com/nitrotext/NitroLineHeightSpan.kt create mode 100644 android/src/main/java/com/nitrotext/NitroTextImpl.kt create mode 100755 android/src/main/java/com/nitrotext/NitroTextPackage.kt create mode 100644 android/src/main/java/com/nitrotext/NitroTextView.kt create mode 100644 android/src/main/java/com/nitrotext/NitroTextViewManager.kt rename ios/NitroTextComponentDescriptor.mm => cpp/NitroTextComponentDescriptor.cpp (90%) create mode 100644 cpp/NitroTextComponentDescriptor.hpp rename ios/{NitroTextShadowOverride.mm => HybridNitroTextComponent+ShadowOverride.mm} (93%) delete mode 100644 ios/NitroTextComponentDescriptor.hpp create mode 100644 nitrogen/generated/android/NitroText+autolinking.cmake create mode 100644 nitrogen/generated/android/NitroText+autolinking.gradle create mode 100644 nitrogen/generated/android/NitroTextOnLoad.cpp create mode 100644 nitrogen/generated/android/NitroTextOnLoad.hpp create mode 100644 nitrogen/generated/android/c++/JDynamicTypeRamp.hpp create mode 100644 nitrogen/generated/android/c++/JEllipsizeMode.hpp create mode 100644 nitrogen/generated/android/c++/JFontStyle.hpp create mode 100644 nitrogen/generated/android/c++/JFontWeight.hpp create mode 100644 nitrogen/generated/android/c++/JFragment.hpp create mode 100644 nitrogen/generated/android/c++/JFunc_void.hpp create mode 100644 nitrogen/generated/android/c++/JFunc_void_TextLayoutEvent.hpp create mode 100644 nitrogen/generated/android/c++/JHybridNitroTextSpec.cpp create mode 100644 nitrogen/generated/android/c++/JHybridNitroTextSpec.hpp create mode 100644 nitrogen/generated/android/c++/JLineBreakStrategyIOS.hpp create mode 100644 nitrogen/generated/android/c++/JTextAlign.hpp create mode 100644 nitrogen/generated/android/c++/JTextDecorationLine.hpp create mode 100644 nitrogen/generated/android/c++/JTextDecorationStyle.hpp create mode 100644 nitrogen/generated/android/c++/JTextLayout.hpp create mode 100644 nitrogen/generated/android/c++/JTextLayoutEvent.hpp create mode 100644 nitrogen/generated/android/c++/JTextTransform.hpp create mode 100644 nitrogen/generated/android/c++/views/JHybridNitroTextStateUpdater.cpp create mode 100644 nitrogen/generated/android/c++/views/JHybridNitroTextStateUpdater.hpp create mode 100644 nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/DynamicTypeRamp.kt create mode 100644 nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/EllipsizeMode.kt create mode 100644 nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/FontStyle.kt create mode 100644 nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/FontWeight.kt create mode 100644 nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/Fragment.kt create mode 100644 nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/Func_void.kt create mode 100644 nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/Func_void_TextLayoutEvent.kt create mode 100644 nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/HybridNitroTextSpec.kt create mode 100644 nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/LineBreakStrategyIOS.kt create mode 100644 nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/NitroTextOnLoad.kt create mode 100644 nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/TextAlign.kt create mode 100644 nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/TextDecorationLine.kt create mode 100644 nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/TextDecorationStyle.kt create mode 100644 nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/TextLayout.kt create mode 100644 nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/TextLayoutEvent.kt create mode 100644 nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/TextTransform.kt create mode 100644 nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/views/HybridNitroTextManager.kt create mode 100644 nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/views/HybridNitroTextStateUpdater.kt create mode 100644 post-script.js diff --git a/android/CMakeLists.txt b/android/CMakeLists.txt new file mode 100644 index 0000000..740acc8 --- /dev/null +++ b/android/CMakeLists.txt @@ -0,0 +1,43 @@ +project(NitroText) +cmake_minimum_required(VERSION 3.9.0) + +set (PACKAGE_NAME NitroText) +set (CMAKE_VERBOSE_MAKEFILE ON) +set (CMAKE_CXX_STANDARD 20) + +# Enable Raw Props parsing in react-native (for Nitro Views) +add_compile_options(-DRN_SERIALIZABLE_STATE=1) + +# Define C++ library and add all sources +add_library(${PACKAGE_NAME} SHARED + src/main/cpp/cpp-adapter.cpp +) + +# Add Nitrogen specs :) +include(${CMAKE_SOURCE_DIR}/../nitrogen/generated/android/NitroText+autolinking.cmake) + +# Set up local includes +include_directories( + "src/main/cpp" + "../cpp" +) + +# Add custom ShadowNode implementation +target_sources( + ${PACKAGE_NAME} PRIVATE + ../cpp/NitroTextShadowNode.cpp + ../cpp/NitroTextShadowNode.hpp + ../cpp/NitroTextComponentDescriptor.cpp + ../cpp/NitroTextComponentDescriptor.hpp + src/main/cpp/NitroTextRegisterProvider.cpp + .. +) + +find_library(LOG_LIB log) + +# Link all libraries together +target_link_libraries( + ${PACKAGE_NAME} + ${LOG_LIB} + android # <-- Android core +) diff --git a/android/build.gradle b/android/build.gradle new file mode 100644 index 0000000..a1abdce --- /dev/null +++ b/android/build.gradle @@ -0,0 +1,148 @@ +buildscript { + repositories { + google() + mavenCentral() + } + + dependencies { + classpath "com.android.tools.build:gradle:8.12.1" + } +} + +def reactNativeArchitectures() { + def value = rootProject.getProperties().get("reactNativeArchitectures") + return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"] +} + +def isNewArchitectureEnabled() { + return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true" +} + +apply plugin: "com.android.library" +apply plugin: 'org.jetbrains.kotlin.android' +apply from: '../nitrogen/generated/android/NitroText+autolinking.gradle' + +if (isNewArchitectureEnabled()) { + apply plugin: "com.facebook.react" +} + +def getExtOrDefault(name) { + return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties["NitroText_" + name] +} + +def getExtOrIntegerDefault(name) { + return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["NitroText_" + name]).toInteger() +} + +android { + namespace "com.nitrotext" + + ndkVersion getExtOrDefault("ndkVersion") + compileSdkVersion getExtOrIntegerDefault("compileSdkVersion") + + defaultConfig { + minSdkVersion getExtOrIntegerDefault("minSdkVersion") + targetSdkVersion getExtOrIntegerDefault("targetSdkVersion") + buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString() + + externalNativeBuild { + cmake { + cppFlags "-frtti -fexceptions -Wall -Wextra -fstack-protector-all" + arguments "-DANDROID_STL=c++_shared", "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON" + abiFilters (*reactNativeArchitectures()) + + buildTypes { + debug { + cppFlags "-O1 -g" + } + release { + cppFlags "-O2" + } + } + } + } + } + + externalNativeBuild { + cmake { + path "CMakeLists.txt" + } + } + + packagingOptions { + excludes = [ + "META-INF", + "META-INF/**", + "**/libc++_shared.so", + "**/libfbjni.so", + "**/libjsi.so", + "**/libfolly_json.so", + "**/libfolly_runtime.so", + "**/libglog.so", + "**/libhermes.so", + "**/libhermes-executor-debug.so", + "**/libhermes_executor.so", + "**/libreactnative.so", + "**/libreactnativejni.so", + "**/libturbomodulejsijni.so", + "**/libreact_nativemodule_core.so", + "**/libjscexecutor.so" + ] + } + + buildFeatures { + buildConfig true + prefab true + } + + buildTypes { + release { + minifyEnabled false + } + } + + lintOptions { + disable "GradleCompatible" + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + sourceSets { + main { + if (isNewArchitectureEnabled()) { + java.srcDirs += [ + // React Codegen files + "${project.buildDir}/generated/source/codegen/java" + ] + } + } + } +} + +repositories { + mavenCentral() + google() +} + + +dependencies { + // For < 0.71, this will be from the local maven repo + // For > 0.71, this will be replaced by `com.facebook.react:react-android:$version` by react gradle plugin + //noinspection GradleDynamicVersion + implementation "com.facebook.react:react-native:+" + + // Add a dependency on NitroModules + implementation project(":react-native-nitro-modules") + +} + +if (isNewArchitectureEnabled()) { + react { + jsRootDir = file("../src/") + libraryName = "NitroText" + codegenJavaPackageName = "com.nitrotext" + } +} \ No newline at end of file diff --git a/android/gradle.properties b/android/gradle.properties new file mode 100644 index 0000000..9b5affa --- /dev/null +++ b/android/gradle.properties @@ -0,0 +1,5 @@ +NitroText_kotlinVersion=2.0.21 +NitroText_minSdkVersion=29 +NitroText_targetSdkVersion=34 +NitroText_compileSdkVersion=34 +NitroText_ndkVersion=27.1.12297006 diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml new file mode 100644 index 0000000..a2f47b6 --- /dev/null +++ b/android/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + diff --git a/android/src/main/cpp/NitroTextRegisterProvider.cpp b/android/src/main/cpp/NitroTextRegisterProvider.cpp new file mode 100644 index 0000000..2a7bf57 --- /dev/null +++ b/android/src/main/cpp/NitroTextRegisterProvider.cpp @@ -0,0 +1,25 @@ +// +// NitroTextRegisterProvider.cpp +// Registers custom ComponentDescriptor for NitroText on Android +// + +#include "NitroTextRegisterProvider.hpp" + +#include +#include +#include "../../../cpp/NitroTextComponentDescriptor.hpp" + +namespace margelo::nitro::nitrotext { + +// Call this from JNI_OnLoad after nitrogen initialization +void registerNitroTextComponentDescriptor() { + using namespace facebook::react; + using margelo::nitro::nitrotext::views::NitroTextComponentDescriptor; + + auto provider = concreteComponentDescriptorProvider(); + auto providerRegistry = CoreComponentsRegistry::sharedProviderRegistry(); + // Add/override provider for component name "NitroText" (HybridNitroTextComponentName) + providerRegistry->add(provider); +} + +} // namespace margelo::nitro::nitrotext diff --git a/android/src/main/cpp/NitroTextRegisterProvider.hpp b/android/src/main/cpp/NitroTextRegisterProvider.hpp new file mode 100644 index 0000000..c766bad --- /dev/null +++ b/android/src/main/cpp/NitroTextRegisterProvider.hpp @@ -0,0 +1,13 @@ +// +// NitroTextRegisterProvider.hpp +// Declares helper to register custom ComponentDescriptor +// + +#pragma once + +namespace margelo::nitro::nitrotext { + +void registerNitroTextComponentDescriptor(); + +} + diff --git a/android/src/main/cpp/cpp-adapter.cpp b/android/src/main/cpp/cpp-adapter.cpp new file mode 100644 index 0000000..8701791 --- /dev/null +++ b/android/src/main/cpp/cpp-adapter.cpp @@ -0,0 +1,8 @@ +#include +#include "NitroTextOnLoad.hpp" +#include "NitroTextRegisterProvider.hpp" + +JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) { + margelo::nitro::nitrotext::registerNitroTextComponentDescriptor(); + return margelo::nitro::nitrotext::initialize(vm); +} diff --git a/android/src/main/java/com/nitrotext/HybridNitroText.kt b/android/src/main/java/com/nitrotext/HybridNitroText.kt new file mode 100755 index 0000000..206e520 --- /dev/null +++ b/android/src/main/java/com/nitrotext/HybridNitroText.kt @@ -0,0 +1,178 @@ +package com.nitrotext + +import androidx.annotation.Keep +import com.facebook.proguard.annotations.DoNotStrip +import com.facebook.react.uimanager.ThemedReactContext +import com.margelo.nitro.nitrotext.* + +@Keep +@DoNotStrip +class HybridNitroText(val context: ThemedReactContext) : HybridNitroTextSpec(), NitroTextViewDelegate { + override val view: NitroTextView = NitroTextView(context) + private val impl = NitroTextImpl(view.textView) + private var onTextLayoutCallback: ((TextLayoutEvent) -> Unit)? = null + + init { + view.nitroTextDelegate = this + } + + override var fragments: Array? + get() = null + set(value) { + impl.setFragments(value) + } + + override var selectable: Boolean? + get() = null + set(value) { + impl.setSelectable(value) + } + + override var allowFontScaling: Boolean? + get() = null + set(value) { + impl.setAllowFontScaling(value) + } + + override var ellipsizeMode: EllipsizeMode? + get() = null + set(value) { + impl.setEllipsizeMode(value) + } + + override var numberOfLines: Double? + get() = null + set(value) { + impl.setNumberOfLines(value) + } + + override var lineBreakStrategyIOS: LineBreakStrategyIOS? + get() = null + set(value) { + // iOS only + } + + override var dynamicTypeRamp: DynamicTypeRamp? + get() = null + set(value) { + // iOS only + } + + override var maxFontSizeMultiplier: Double? + get() = null + set(value) { + impl.setMaxFontSizeMultiplier(value) + } + + override var adjustsFontSizeToFit: Boolean? + get() = null + set(value) { + // TODO: Implement adjustsFontSizeToFit + } + + override var minimumFontScale: Double? + get() = null + set(value) { value } + + override var onTextLayout: ((TextLayoutEvent) -> Unit)? + get() = onTextLayoutCallback + set(value) { + onTextLayoutCallback = value + } + + override var onPress: (() -> Unit)? + get() = null + set(value) { + // TODO: Implement onPress + } + + override var onPressIn: (() -> Unit)? + get() = null + set(value) { + // TODO: Implement onPressIn + } + + override var onPressOut: (() -> Unit)? + get() = null + set(value) { + // TODO: Implement onPressOut + } + + override var text: String? + get() = null + set(value) { + impl.setText(value) + } + + override var selectionColor: String? + get() = null + set(value) { + impl.setSelectionColor(value) + } + + override var fontSize: Double? + get() = null + set(value) { impl.setFontSize(value) } + + override var fontWeight: FontWeight? + get() = null + set(value) { impl.setFontWeight(value) } + + override var fontColor: String? + get() = null + set(value) { + impl.setFontColor(value) + } + + override var fragmentBackgroundColor: String? + get() = null + set(value) { + impl.setFragmentBackgroundColor(value) + } + + override var fontStyle: FontStyle? + get() = null + set(value) { impl.setFontStyle(value) } + + override var fontFamily: String? + get() = null + set(value) { impl.setFontFamily(value) } + + override var lineHeight: Double? + get() = null + set(value) { impl.setLineHeight(value) } + + override var letterSpacing: Double? + get() = null + set(value) { impl.setLetterSpacing(value) } + + override var textAlign: TextAlign? + get() = null + set(value) { impl.setTextAlign(value) } + + override var textTransform: TextTransform? + get() = null + set(value) { impl.setTextTransform(value) } + + override var textDecorationLine: TextDecorationLine? + get() = null + set(value) { impl.setTextDecorationLine(value) } + + override var textDecorationColor: String? + get() = null + set(value) {impl.setTextDecorationColor(value) } + + override var textDecorationStyle: TextDecorationStyle? + get() = null + set(value) { impl.setTextDecorationStyle(value) } + + override fun afterUpdate() { + impl.commit() + view.requestLayout() + view.invalidate() + } + + override fun onNitroTextLayout(event: TextLayoutEvent) { + onTextLayoutCallback?.invoke(event) + } +} diff --git a/android/src/main/java/com/nitrotext/NitroLineHeightSpan.kt b/android/src/main/java/com/nitrotext/NitroLineHeightSpan.kt new file mode 100644 index 0000000..cc1487a --- /dev/null +++ b/android/src/main/java/com/nitrotext/NitroLineHeightSpan.kt @@ -0,0 +1,29 @@ +package com.nitrotext + +import android.graphics.Paint +import android.text.style.LineHeightSpan +import kotlin.math.ceil +import kotlin.math.floor + +/** + * Matches React Native's CustomLineHeightSpan so lineHeight behaves the same as . + */ +class NitroLineHeightSpan(heightPx: Float) : LineHeightSpan { + private val lineHeight: Int = ceil(heightPx.toDouble()).toInt() + + override fun chooseHeight( + text: CharSequence, + start: Int, + end: Int, + spanstartv: Int, + v: Int, + fm: Paint.FontMetricsInt + ) { + val leading = lineHeight - ((-fm.ascent) + fm.descent) + val halfLeading = leading / 2f + fm.ascent -= ceil(halfLeading.toDouble()).toInt() + fm.descent += floor(halfLeading.toDouble()).toInt() + if (start == 0) fm.top = fm.ascent + if (end == text.length) fm.bottom = fm.descent + } +} diff --git a/android/src/main/java/com/nitrotext/NitroTextImpl.kt b/android/src/main/java/com/nitrotext/NitroTextImpl.kt new file mode 100644 index 0000000..79a0b3c --- /dev/null +++ b/android/src/main/java/com/nitrotext/NitroTextImpl.kt @@ -0,0 +1,301 @@ +package com.nitrotext + +import android.graphics.Color +import android.graphics.Typeface +import android.text.Spannable +import android.text.SpannableStringBuilder +import android.text.TextUtils +import android.text.style.AbsoluteSizeSpan +import android.text.style.BackgroundColorSpan +import android.text.style.ForegroundColorSpan +import android.text.style.StrikethroughSpan +import android.text.style.StyleSpan +import android.text.style.TypefaceSpan +import android.text.style.UnderlineSpan +import android.view.Gravity +import androidx.appcompat.widget.AppCompatTextView +import com.facebook.react.uimanager.PixelUtil +import com.margelo.nitro.nitrotext.* +import androidx.core.graphics.toColorInt + +class NitroTextImpl(private val view: AppCompatTextView) { + // Stored props + private var fragments: Array? = null + private var text: String? = null + + private var selectable: Boolean? = null + private var selectionColor: String? = null + private var numberOfLines: Double? = null + private var ellipsizeMode: EllipsizeMode? = null + private var allowFontScaling: Boolean = true + private var maxFontSizeMultiplier: Double? = null + + // Top-level styling (applied when using simple text) + private var fontSize: Double? = null + private var fontWeight: FontWeight? = null + private var fontColor: String? = null + private var fontStyle: FontStyle? = null + private var fontFamily: String? = null + private var letterSpacing: Double? = null + private var lineHeight: Double? = null + private var textAlign: TextAlign? = null + private var textTransform: TextTransform? = null + private var textDecorationLine: TextDecorationLine? = null + private var textDecorationColor: String? = null + private var textDecorationStyle: TextDecorationStyle? = null + + private var fragmentBackgroundColor: String? = null + + fun commit() { + // Reset typography to avoid stale values from recycled views + view.setLineSpacing(0f, 1f) + view.letterSpacing = 0f + applySelectable() + applySelectionColor() + applyLinesAndEllipsize() + applyAlignment() + + val frags = fragments + if (!frags.isNullOrEmpty()) { + applyFragments(frags) + } else { + applySimpleText() + } + + applyLetterSpacing() + } + + // Setters + fun setFragments(value: Array?) { fragments = value } + fun setText(value: String?) { text = value } + + fun setSelectable(value: Boolean?) { selectable = value } + fun setSelectionColor(value: String?) { selectionColor = value } + fun setNumberOfLines(value: Double?) { numberOfLines = value } + fun setEllipsizeMode(value: EllipsizeMode?) { ellipsizeMode = value } + fun setAllowFontScaling(value: Boolean?) { allowFontScaling = value ?: true } + fun setMaxFontSizeMultiplier(value: Double?) { maxFontSizeMultiplier = value } + + fun setFontSize(value: Double?) { fontSize = value } + fun setFontWeight(value: FontWeight?) { fontWeight = value } + fun setFontColor(value: String?) { fontColor = value } + fun setFontStyle(value: FontStyle?) { fontStyle = value } + fun setFontFamily(value: String?) { fontFamily = value } + fun setLetterSpacing(value: Double?) { letterSpacing = value } + fun setLineHeight(value: Double?) { lineHeight = value } + fun setTextAlign(value: TextAlign?) { textAlign = value } + fun setTextTransform(value: TextTransform?) { textTransform = value } + fun setTextDecorationLine(value: TextDecorationLine?) { textDecorationLine = value } + fun setTextDecorationColor(value: String?) { textDecorationColor = value } + fun setTextDecorationStyle(value: TextDecorationStyle?) { textDecorationStyle = value } + + fun setFragmentBackgroundColor(value: String?) { + fragmentBackgroundColor = value + } + + // Apply helpers + private fun applySelectable() { + selectable?.let { view.setTextIsSelectable(it) } + } + + private fun applySelectionColor() { + selectionColor?.let { colorString -> + parseColorSafe(colorString)?.let { view.highlightColor = it } + } + } + + private fun applyLinesAndEllipsize() { + val lines = numberOfLines?.toInt() ?: Int.MAX_VALUE + view.maxLines = if (lines <= 0) Int.MAX_VALUE else lines + view.isSingleLine = (lines == 1) + + view.ellipsize = when (ellipsizeMode) { + EllipsizeMode.HEAD -> TextUtils.TruncateAt.START + EllipsizeMode.MIDDLE -> TextUtils.TruncateAt.MIDDLE + EllipsizeMode.TAIL -> TextUtils.TruncateAt.END + EllipsizeMode.CLIP -> null + else -> TextUtils.TruncateAt.END + } + } + + private fun applyLetterSpacing() { + letterSpacing?.let { spacingPx -> + val spacingPxFloat = if (allowFontScaling) { + PixelUtil.toPixelFromSP(spacingPx.toFloat(), maxFontScale()) + } else { + PixelUtil.toPixelFromDIP(spacingPx.toFloat()) + } + val textSizePx = view.textSize + if (textSizePx > 0f) { + val em = spacingPxFloat / textSizePx + view.letterSpacing = em + } + } + } + + private fun applyAlignment() { + val verticalCenter = Gravity.CENTER_VERTICAL + view.gravity = when (textAlign) { + TextAlign.LEFT -> Gravity.START or verticalCenter + TextAlign.RIGHT -> Gravity.END or verticalCenter + TextAlign.CENTER -> Gravity.CENTER_HORIZONTAL or verticalCenter + TextAlign.JUSTIFY, TextAlign.AUTO, null -> Gravity.START or verticalCenter + } + } + + private fun applySimpleText() { + val content = transformText(text, textTransform) + val lineHeightPx = resolveLineHeight(lineHeight) + if (content != null) { + val spansRequired = textDecorationLine != null || lineHeightPx != null + if (spansRequired) { + val builder = SpannableStringBuilder(content) + applyDecorationSpans(builder, 0, builder.length, textDecorationLine) + lineHeightPx?.let { applyLineHeightSpan(builder, 0, builder.length, it) } + view.text = builder + } else { + view.text = content + } + } else { + view.text = "" + } + + view.setTextColor(resolvedFontColor()) + fontSize?.let { + if (allowFontScaling) { + view.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, it.toFloat()) + } else { + val px = PixelUtil.toPixelFromDIP(it.toFloat()) + view.setTextSize(android.util.TypedValue.COMPLEX_UNIT_PX, px) + } + } + + val style = combineStyle(fontWeight, fontStyle) + if (style != Typeface.NORMAL) view.setTypeface(view.typeface, style) + } + + private fun applyFragments(fragments: Array) { + val builder = SpannableStringBuilder() + val containerLineHeightPx = resolveLineHeight(lineHeight) + var start: Int + for (frag in fragments) { + val fragText = transformText(frag.text, frag.textTransform) ?: "" + start = builder.length + builder.append(fragText) + val end = builder.length + if (start == end) continue + + frag.fontColor?.let { parseColorSafe(it)?.let { c -> + builder.setSpan(ForegroundColorSpan(c), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + }} + frag.fragmentBackgroundColor?.let { parseColorSafe(it)?.let { c -> + builder.setSpan(BackgroundColorSpan(c), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + }} + // Font Size in SP + frag.fontSize?.let { sz -> + builder.setSpan(AbsoluteSizeSpan(sz.toInt(), true), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + } + // Font Weight/Style + val style = combineStyle(frag.fontWeight, frag.fontStyle) + if (style != Typeface.NORMAL) { + builder.setSpan(StyleSpan(style), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + } + // Font Family + frag.fontFamily?.let { + builder.setSpan(TypefaceSpan(it), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + } + // Decorations + applyDecorationSpans(builder, start, end, frag.textDecorationLine) + frag.lineHeight?.let { lh -> + resolveLineHeight(lh)?.let { applyLineHeightSpan(builder, start, end, it) } + } + } + containerLineHeightPx?.let { applyLineHeightSpan(builder, 0, builder.length, it) } + view.text = builder + + // Apply default text color for runs without explicit color + view.setTextColor(resolvedFontColor()) + } + + private fun applyDecorationSpans(builder: SpannableStringBuilder, start: Int, end: Int, line: TextDecorationLine?) { + when (line) { + TextDecorationLine.UNDERLINE -> builder.setSpan(UnderlineSpan(), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + TextDecorationLine.LINE_THROUGH -> builder.setSpan(StrikethroughSpan(), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + TextDecorationLine.UNDERLINE_LINE_THROUGH -> { + builder.setSpan(UnderlineSpan(), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + builder.setSpan(StrikethroughSpan(), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + } + else -> Unit + } + } + + private fun combineStyle(weight: FontWeight?, style: FontStyle?): Int { + val isBold = when (weight) { + FontWeight.BOLD, FontWeight.SEMIBOLD, FontWeight.HEAVY, FontWeight.BLACK, FontWeight.CONDENSEDBOLD -> true + else -> false + } + val isItalic = when (style) { + FontStyle.ITALIC, FontStyle.OBLIQUE -> true + else -> false + } + return when { + isBold && isItalic -> Typeface.BOLD_ITALIC + isBold -> Typeface.BOLD + isItalic -> Typeface.ITALIC + else -> Typeface.NORMAL + } + } + + private fun transformText(text: String?, transform: TextTransform?): String? { + if (text == null) return null + return when (transform) { + TextTransform.UPPERCASE -> text.uppercase() + TextTransform.LOWERCASE -> text.lowercase() + TextTransform.CAPITALIZE -> text.split(" ").joinToString(" ") { it.replaceFirstChar { c -> c.uppercase() } } + else -> text + } + } + + private fun parseColorSafe(str: String): Int? = try { + str.toColorInt() + } catch (_: Throwable) { null } + + private fun resolvedFontColor(): Int { + val parsed = fontColor?.let { parseColorSafe(it) } + return parsed ?: Color.BLACK + } + + private fun resolveLineHeight(value: Double?): Float? { + val raw = value ?: return null + val px = if (allowFontScaling) { + PixelUtil.toPixelFromSP(raw.toFloat(), maxFontScale()) + } else { + PixelUtil.toPixelFromDIP(raw.toFloat()) + } + return px.takeIf { it > 0f } + } + + private fun maxFontScale(): Float { + val multiplier = maxFontSizeMultiplier + return if (multiplier != null && multiplier >= 1.0) multiplier.toFloat() else Float.NaN + } + + private fun applyLineHeightSpan( + builder: SpannableStringBuilder, + start: Int, + end: Int, + lineHeightPx: Float + ) { + if (start >= end) return + val flags = if (start == 0) { + Spannable.SPAN_INCLUSIVE_INCLUSIVE + } else { + Spannable.SPAN_EXCLUSIVE_INCLUSIVE + } + builder.setSpan(NitroLineHeightSpan(lineHeightPx), start, end, flags) + } + + companion object { + private const val TAG = "NitroTextImpl" + } +} diff --git a/android/src/main/java/com/nitrotext/NitroTextPackage.kt b/android/src/main/java/com/nitrotext/NitroTextPackage.kt new file mode 100755 index 0000000..75aa903 --- /dev/null +++ b/android/src/main/java/com/nitrotext/NitroTextPackage.kt @@ -0,0 +1,27 @@ +package com.nitrotext + +import com.facebook.react.bridge.NativeModule +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.uimanager.ViewManager +import com.facebook.react.TurboReactPackage +import com.facebook.react.module.model.ReactModuleInfoProvider +import com.margelo.nitro.nitrotext.NitroTextOnLoad +import java.util.HashMap + +class NitroTextPackage : TurboReactPackage() { + override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? = null + + override fun getReactModuleInfoProvider(): ReactModuleInfoProvider = ReactModuleInfoProvider { HashMap() } + + override fun createViewManagers(reactContext: ReactApplicationContext): List> { + val viewManagers = ArrayList>() + viewManagers.add(NitroTextViewManager()); + return viewManagers; + } + + companion object { + init { + NitroTextOnLoad.initializeNative() + } + } +} diff --git a/android/src/main/java/com/nitrotext/NitroTextView.kt b/android/src/main/java/com/nitrotext/NitroTextView.kt new file mode 100644 index 0000000..6ae7e14 --- /dev/null +++ b/android/src/main/java/com/nitrotext/NitroTextView.kt @@ -0,0 +1,133 @@ +package com.nitrotext + +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.Rect +import android.graphics.text.LineBreaker +import android.text.Layout +import android.text.TextPaint +import android.view.View +import androidx.appcompat.widget.AppCompatTextView +import com.facebook.react.views.view.ReactViewGroup +import com.margelo.nitro.nitrotext.TextLayout +import com.margelo.nitro.nitrotext.TextLayoutEvent + + +interface NitroTextViewDelegate { + fun onNitroTextLayout(event: TextLayoutEvent) +} + +@SuppressLint("ViewConstructor") +class NitroTextView(ctx: Context) : ReactViewGroup(ctx) { + val textView = AppCompatTextView(ctx).apply { + includeFontPadding = true + minWidth = 0; minHeight = 0 + breakStrategy = LineBreaker.BREAK_STRATEGY_HIGH_QUALITY + hyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NORMAL + setHorizontallyScrolling(false) + } + + var nitroTextDelegate: NitroTextViewDelegate? = null + set(value) { + field = value + scheduleTextLayoutDispatch() + } + + private var pendingLayoutDispatch = false + + init { + // Fill the container; borders/radius are applied to this container by RN. + addView( + textView, + LayoutParams( + LayoutParams.MATCH_PARENT, + LayoutParams.MATCH_PARENT + ) + ) + } + + override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { + val childLeft = paddingLeft + val childTop = paddingTop + val childRight = measuredWidth - paddingRight + val childBottom = measuredHeight - paddingBottom + textView.layout(childLeft, childTop, childRight, childBottom) + scheduleTextLayoutDispatch() + } + + // Block adding of any other children to this container (we only have a single TextView) + override fun addView(child: View?, index: Int) { + if (child === textView) super.addView(child, index) + } + + override fun addView(child: View?, index: Int, params: LayoutParams?) { + if (child === textView) super.addView(child, index, params) + } + + private fun scheduleTextLayoutDispatch() { + val delegate = nitroTextDelegate ?: return + if (pendingLayoutDispatch) return + pendingLayoutDispatch = true + textView.post { + pendingLayoutDispatch = false + val event = buildTextLayoutEvent() + if (event != null) { + delegate.onNitroTextLayout(event) + } else if (nitroTextDelegate != null) { + scheduleTextLayoutDispatch() + } + } + } + + private fun buildTextLayoutEvent(): TextLayoutEvent? { + val layout = textView.layout ?: return null + val content = textView.text ?: return TextLayoutEvent(emptyArray()) + if (content.isEmpty()) return TextLayoutEvent(emptyArray()) + + val density = resources.displayMetrics.density + + val paintCopy = TextPaint(textView.paint).apply { textSize *= 100f } + val capBounds = Rect() + paintCopy.getTextBounds("T", 0, 1, capBounds) + val capHeight = capBounds.height() / 100f / density + + val xBounds = Rect() + paintCopy.getTextBounds("x", 0, 1, xBounds) + val xHeight = xBounds.height() / 100f / density + + val totalLines = layout.lineCount + val leftOffset = textView.left + textView.totalPaddingLeft + val topOffset = textView.top + textView.totalPaddingTop + val lines = Array(totalLines) { lineIndex -> + val start = layout.getLineStart(lineIndex) + val end = layout.getLineEnd(lineIndex) + val lineText = content.subSequence(start, end).toString() + + val endsWithNewline = end > start && content[end - 1] == '\n' + val widthPx = if (endsWithNewline) layout.getLineMax(lineIndex) else layout.getLineWidth(lineIndex) + val lineLeft = layout.getLineLeft(lineIndex) + val lineTop = layout.getLineTop(lineIndex) + val lineBottom = layout.getLineBottom(lineIndex) + + val x = (leftOffset + lineLeft) / density + val y = (topOffset + lineTop) / density + val height = (lineBottom - lineTop) / density + val descender = layout.getLineDescent(lineIndex) / density + val ascender = -layout.getLineAscent(lineIndex) / density + + TextLayout( + text = lineText, + x = x.toDouble(), + y = y.toDouble(), + width = (widthPx / density).toDouble(), + height = height.toDouble(), + descender = descender.toDouble(), + capHeight = capHeight.toDouble(), + ascender = ascender.toDouble(), + xHeight = xHeight.toDouble() + ) + } + + return TextLayoutEvent(lines) + } +} diff --git a/android/src/main/java/com/nitrotext/NitroTextViewManager.kt b/android/src/main/java/com/nitrotext/NitroTextViewManager.kt new file mode 100644 index 0000000..8e2687b --- /dev/null +++ b/android/src/main/java/com/nitrotext/NitroTextViewManager.kt @@ -0,0 +1,48 @@ +package com.nitrotext + +import com.facebook.react.uimanager.ReactStylesDiffMap +import com.facebook.react.uimanager.StateWrapper +import com.facebook.react.uimanager.ThemedReactContext +import com.facebook.react.views.view.ReactViewGroup +import com.facebook.react.views.view.ReactViewManager +import com.margelo.nitro.nitrotext.views.HybridNitroTextStateUpdater + + +/** + * Represents the React Native `ViewManager` for the "NitroText" Nitro HybridView. + */ +open class NitroTextViewManager: ReactViewManager() { + private val views = hashMapOf() + + override fun getName(): String { + return "NitroText" + } + + override fun createViewInstance(context: ThemedReactContext): ReactViewGroup { + val hybridView = HybridNitroText(context) + val view = hybridView.view + views[view] = hybridView + return view + } + + override fun setPadding(view: ReactViewGroup?, left: Int, top: Int, right: Int, bottom: Int) { + view?.setPadding(left, top, right, bottom) + } + + override fun onDropViewInstance(view: ReactViewGroup) { + super.onDropViewInstance(view) + views.remove(view) + } + + override fun updateState(view: ReactViewGroup, props: ReactStylesDiffMap, stateWrapper: StateWrapper): Any? { + val hybridView = views[view] ?: throw Error("Couldn't find view $view in local views table!") + + // 1. Update each prop individually + hybridView.beforeUpdate() + HybridNitroTextStateUpdater.updateViewProps(hybridView, stateWrapper) + hybridView.afterUpdate() + + // 2. Continue in base View props + return super.updateState(view, props, stateWrapper) + } +} diff --git a/ios/NitroTextComponentDescriptor.mm b/cpp/NitroTextComponentDescriptor.cpp similarity index 90% rename from ios/NitroTextComponentDescriptor.mm rename to cpp/NitroTextComponentDescriptor.cpp index e7402d7..3314257 100644 --- a/ios/NitroTextComponentDescriptor.mm +++ b/cpp/NitroTextComponentDescriptor.cpp @@ -1,10 +1,11 @@ // -// NitroTextComponentDescriptor.mm -// Implementation for custom ComponentDescriptor +// NitroTextComponentDescriptor.cpp +// Shared implementation for custom ComponentDescriptor // -#import "NitroTextComponentDescriptor.hpp" -#import +#include "NitroTextComponentDescriptor.hpp" + +#include using namespace facebook; using namespace margelo::nitro::nitrotext::views; @@ -38,6 +39,7 @@ // Inject TextLayoutManager so measurement works on Fabric (iOS/macOS/etc.). // Construct directly with the descriptor's ContextContainer. + if (auto contextContainer = this->getContextContainer()) { auto textLayoutManager = std::make_shared(contextContainer); concreteShadowNode.setTextLayoutManager(textLayoutManager); diff --git a/cpp/NitroTextComponentDescriptor.hpp b/cpp/NitroTextComponentDescriptor.hpp new file mode 100644 index 0000000..4a7dd97 --- /dev/null +++ b/cpp/NitroTextComponentDescriptor.hpp @@ -0,0 +1,30 @@ +// +// NitroTextComponentDescriptor.hpp (shared) +// Custom, non-generated ComponentDescriptor for NitroText +// + +#pragma once + +#include "NitroTextShadowNode.hpp" +#include + +namespace margelo::nitro::nitrotext::views { + /** + * The Component Descriptor for the "NitroText" View. + */ + class NitroTextComponentDescriptor final: public react::ConcreteComponentDescriptor { + public: + NitroTextComponentDescriptor(const react::ComponentDescriptorParameters& parameters); + + public: + /** + * A faster path for cloning props - reuses the caching logic from `HybridNitroTextProps`. + */ + std::shared_ptr cloneProps(const react::PropsParserContext& context, + const std::shared_ptr& props, + react::RawProps rawProps) const override; + + void adopt(react::ShadowNode& shadowNode) const override; + }; + + } // namespace margelo::nitro::nitrotext::views diff --git a/cpp/NitroTextShadowNode.cpp b/cpp/NitroTextShadowNode.cpp index 3c999bc..75770ab 100644 --- a/cpp/NitroTextShadowNode.cpp +++ b/cpp/NitroTextShadowNode.cpp @@ -16,6 +16,14 @@ #include #endif +#if defined(REACT_NATIVE_VERSION_MAJOR) +#define RN_VERSION_AT_LEAST(major, minor) \ + ((REACT_NATIVE_VERSION_MAJOR > (major)) || \ + (REACT_NATIVE_VERSION_MAJOR == (major) && REACT_NATIVE_VERSION_MINOR >= (minor))) +#else +#define RN_VERSION_AT_LEAST(major, minor) 0 +#endif + namespace margelo::nitro::nitrotext::views { react::ShadowNodeTraits NitroTextShadowNode::BaseTraits() @@ -366,14 +374,10 @@ react::Size NitroTextShadowNode::measureContent( // but leaving the scale here avoids over-adjusting and potential divergences. if (props.minimumFontScale.value.has_value()) { -#if defined(REACT_NATIVE_VERSION_MAJOR) -#if (REACT_NATIVE_VERSION_MAJOR > 0) || \ - (REACT_NATIVE_VERSION_MAJOR == 0 && REACT_NATIVE_VERSION_MINOR >= 81) - paragraphAttributes.minimumFontScale = - props.minimumFontScale.value.value(); +#if RN_VERSION_AT_LEAST(0, 81) + paragraphAttributes.minimumFontScale = props.minimumFontScale.value.value(); #else // React Native < 0.81 does not expose paragraphAttributes.minimumFontScale yet. -#endif #endif } @@ -411,8 +415,10 @@ react::Size NitroTextShadowNode::measureContent( // Measure using given constraints (Yoga already accounts for padding/border). react::TextLayoutContext textLayoutContext{ .pointScaleFactor = layoutContext.pointScaleFactor, - // TODO: investigate why surfaceId is not working for react-native <= 0.79 -// .surfaceId = this->getSurfaceId(), +#if RN_VERSION_AT_LEAST(0, 81) + // Older React Native versions didn't surface `surfaceId` on TextLayoutContext. + .surfaceId = this->getSurfaceId(), +#endif }; const auto measurement = textLayoutManager_->measure( diff --git a/cpp/NitroTextShadowNode.hpp b/cpp/NitroTextShadowNode.hpp index 68cd7ef..34a0f5a 100644 --- a/cpp/NitroTextShadowNode.hpp +++ b/cpp/NitroTextShadowNode.hpp @@ -5,7 +5,11 @@ #pragma once +#ifdef ANDROID +#include "views/HybridNitroTextComponent.hpp" +#else #include "HybridNitroTextComponent.hpp" +#endif #include #include diff --git a/example/App.tsx b/example/App.tsx index 8995031..283506d 100644 --- a/example/App.tsx +++ b/example/App.tsx @@ -11,7 +11,7 @@ export default function App() { }; const handleTextLayout = (event: TextLayoutEvent) => { - // console.log('lines', lines); + console.log('lines', event.lines); // console.log('width', event); // console.log('height', height); // setLayoutInfo(`Lines: ${lines.length}`); @@ -34,12 +34,15 @@ export default function App() { High-performance selectable text with native rendering + + High-performance selectable text with native rendering + {/* Basic Usage */} Basic Usage - + This is a simple NitroText component with native performance. Try selecting this text to see the smooth selection behavior! @@ -48,7 +51,7 @@ export default function App() { {/* Nested NitroText wth numberOfLines (does not work currently it only renders the first line nested text doesn't render) */} - Nested NitroText with numberOfLines + Nested NitroText with numberOfLines (NitroText) This is a simple NitroText component with native performance.{' '} @@ -57,6 +60,17 @@ export default function App() { + + + Nested NitroText with numberOfLines (RN Text) + + + This is a simple NitroText component with native performance.{' '} + + Try selecting this text to see the smooth selection behavior! + + + {/* Rich Text Formatting */} @@ -145,14 +159,14 @@ export default function App() { NitroText can seamlessly integrate with React Native's Text component: {'\n\n'} - This is a React Native Text component{' '} - with nested NitroText{' '} + This is a React Native Text component + with nested NitroText inside it. {'\n\n'}And vice versa - NitroText can contain:{'\n'} - Regular text with{' '} - RN Text nested inside{' '} + Regular text with + {' '}RN Text nested inside{' '} NitroText. @@ -270,6 +284,8 @@ const styles = StyleSheet.create({ textAlign: 'center', alignSelf: 'flex-start', lineHeight: 24, + borderWidth: 1, + borderColor: 'blue', }, sectionTitle: { fontSize: 24, diff --git a/example/android/build.gradle b/example/android/build.gradle index dad99b0..08d3659 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -1,7 +1,7 @@ buildscript { ext { buildToolsVersion = "36.0.0" - minSdkVersion = 24 + minSdkVersion = 29 compileSdkVersion = 36 targetSdkVersion = 36 ndkVersion = "27.1.12297006" diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 960a603..7b6121e 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -37,7 +37,7 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - NitroText (1.0.2): + - NitroText (1.0.3): - boost - DoubleConversion - fast_float @@ -2644,7 +2644,7 @@ SPEC CHECKSUMS: glog: 5683914934d5b6e4240e497e0f4a3b42d1854183 hermes-engine: 35c763d57c9832d0eef764316ca1c4d043581394 NitroModules: 7d693306799405ca141ef5c24efc0936f20a09c0 - NitroText: 1f69b7c0c89d9f7cbf35fb8cf114dc6ecf8981a6 + NitroText: c95604214333634a6a083ac7211b238686dbd628 RCT-Folly: 846fda9475e61ec7bcbf8a3fe81edfcaeb090669 RCTDeprecation: c0ed3249a97243002615517dff789bf4666cf585 RCTRequired: 58719f5124f9267b5f9649c08bf23d9aea845b23 diff --git a/ios/NitroTextShadowOverride.mm b/ios/HybridNitroTextComponent+ShadowOverride.mm similarity index 93% rename from ios/NitroTextShadowOverride.mm rename to ios/HybridNitroTextComponent+ShadowOverride.mm index 800f38e..b52b5d6 100644 --- a/ios/NitroTextShadowOverride.mm +++ b/ios/HybridNitroTextComponent+ShadowOverride.mm @@ -1,5 +1,5 @@ // -// NitroTextShadowOverride.mm +// HybridNitroTextComponent+ShadowOverride.mm // Override only the ShadowNode/Descriptor for the generated view class // without introducing a new ComponentView class. // @@ -9,7 +9,7 @@ #import #import -#import "NitroTextComponentDescriptor.hpp" +#import "../cpp/NitroTextComponentDescriptor.hpp" // Forward-declare the generated view class; we don't import generated headers here. @interface HybridNitroTextComponent : RCTViewComponentView @@ -39,4 +39,3 @@ + (void)load } @end - diff --git a/ios/NitroTextComponentDescriptor.hpp b/ios/NitroTextComponentDescriptor.hpp deleted file mode 100644 index dda54b1..0000000 --- a/ios/NitroTextComponentDescriptor.hpp +++ /dev/null @@ -1,31 +0,0 @@ -// -// NitroTextComponentDescriptor.hpp -// Custom, non-generated ComponentDescriptor for NitroText -// - -#pragma once - -#include "../cpp/NitroTextShadowNode.hpp" -#include - -namespace margelo::nitro::nitrotext::views { - - /** - * The Component Descriptor for the "NitroText" View. - */ - class NitroTextComponentDescriptor final: public react::ConcreteComponentDescriptor { - public: - NitroTextComponentDescriptor(const react::ComponentDescriptorParameters& parameters); - - public: - /** - * A faster path for cloning props - reuses the caching logic from `HybridNitroTextProps`. - */ - std::shared_ptr cloneProps(const react::PropsParserContext& context, - const std::shared_ptr& props, - react::RawProps rawProps) const override; - - void adopt(react::ShadowNode& shadowNode) const override; - }; - -} // namespace margelo::nitro::nitrotext::views diff --git a/ios/NitroTextImpl.swift b/ios/NitroTextImpl.swift index 85f1341..943d886 100644 --- a/ios/NitroTextImpl.swift +++ b/ios/NitroTextImpl.swift @@ -165,11 +165,6 @@ final class NitroTextImpl { } } - func setPlainText(_ value: String?) { - let attributed = NSAttributedString(string: value ?? "") - setText(attributed) - } - func setTextAlign(_ align: TextAlign?) { switch align { case .some(.center): currentTextAlignment = .center diff --git a/nitro.json b/nitro.json index 34222e1..8ce4525 100644 --- a/nitro.json +++ b/nitro.json @@ -14,7 +14,8 @@ }, "autolinking": { "NitroText": { - "swift": "HybridNitroText" + "swift": "HybridNitroText", + "kotlin": "HybridNitroText" } }, "ignorePaths": [ diff --git a/nitrogen/generated/android/NitroText+autolinking.cmake b/nitrogen/generated/android/NitroText+autolinking.cmake new file mode 100644 index 0000000..f9f7c17 --- /dev/null +++ b/nitrogen/generated/android/NitroText+autolinking.cmake @@ -0,0 +1,83 @@ +# +# NitroText+autolinking.cmake +# This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +# https://github.com/mrousavy/nitro +# Copyright © 2025 Marc Rousavy @ Margelo +# + +# This is a CMake file that adds all files generated by Nitrogen +# to the current CMake project. +# +# To use it, add this to your CMakeLists.txt: +# ```cmake +# include(${CMAKE_SOURCE_DIR}/../nitrogen/generated/android/NitroText+autolinking.cmake) +# ``` + +# Define a flag to check if we are building properly +add_definitions(-DBUILDING_NITROTEXT_WITH_GENERATED_CMAKE_PROJECT) + +# Enable Raw Props parsing in react-native (for Nitro Views) +add_definitions(-DRN_SERIALIZABLE_STATE) + +# Add all headers that were generated by Nitrogen +include_directories( + "../nitrogen/generated/shared/c++" + "../nitrogen/generated/android/c++" + "../nitrogen/generated/android/" +) + +# Add all .cpp sources that were generated by Nitrogen +target_sources( + # CMake project name (Android C++ library name) + NitroText PRIVATE + # Autolinking Setup + ../nitrogen/generated/android/NitroTextOnLoad.cpp + # Shared Nitrogen C++ sources + ../nitrogen/generated/shared/c++/HybridNitroTextSpec.cpp + ../nitrogen/generated/shared/c++/views/HybridNitroTextComponent.cpp + # Android-specific Nitrogen C++ sources + ../nitrogen/generated/android/c++/JHybridNitroTextSpec.cpp + ../nitrogen/generated/android/c++/views/JHybridNitroTextStateUpdater.cpp +) + +# From node_modules/react-native/ReactAndroid/cmake-utils/folly-flags.cmake +# Used in node_modules/react-native/ReactAndroid/cmake-utils/ReactNative-application.cmake +target_compile_definitions( + NitroText PRIVATE + -DFOLLY_NO_CONFIG=1 + -DFOLLY_HAVE_CLOCK_GETTIME=1 + -DFOLLY_USE_LIBCPP=1 + -DFOLLY_CFG_NO_COROUTINES=1 + -DFOLLY_MOBILE=1 + -DFOLLY_HAVE_RECVMMSG=1 + -DFOLLY_HAVE_PTHREAD=1 + # Once we target android-23 above, we can comment + # the following line. NDK uses GNU style stderror_r() after API 23. + -DFOLLY_HAVE_XSI_STRERROR_R=1 +) + +# Add all libraries required by the generated specs +find_package(fbjni REQUIRED) # <-- Used for communication between Java <-> C++ +find_package(ReactAndroid REQUIRED) # <-- Used to set up React Native bindings (e.g. CallInvoker/TurboModule) +find_package(react-native-nitro-modules REQUIRED) # <-- Used to create all HybridObjects and use the Nitro core library + +# Link all libraries together +target_link_libraries( + NitroText + fbjni::fbjni # <-- Facebook C++ JNI helpers + ReactAndroid::jsi # <-- RN: JSI + react-native-nitro-modules::NitroModules # <-- NitroModules Core :) +) + +# Link react-native (different prefab between RN 0.75 and RN 0.76) +if(ReactAndroid_VERSION_MINOR GREATER_EQUAL 76) + target_link_libraries( + NitroText + ReactAndroid::reactnative # <-- RN: Native Modules umbrella prefab + ) +else() + target_link_libraries( + NitroText + ReactAndroid::react_nativemodule_core # <-- RN: TurboModules Core + ) +endif() diff --git a/nitrogen/generated/android/NitroText+autolinking.gradle b/nitrogen/generated/android/NitroText+autolinking.gradle new file mode 100644 index 0000000..3ad0a33 --- /dev/null +++ b/nitrogen/generated/android/NitroText+autolinking.gradle @@ -0,0 +1,27 @@ +/// +/// NitroText+autolinking.gradle +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +/// This is a Gradle file that adds all files generated by Nitrogen +/// to the current Gradle project. +/// +/// To use it, add this to your build.gradle: +/// ```gradle +/// apply from: '../nitrogen/generated/android/NitroText+autolinking.gradle' +/// ``` + +logger.warn("[NitroModules] 🔥 NitroText is boosted by nitro!") + +android { + sourceSets { + main { + java.srcDirs += [ + // Nitrogen files + "${project.projectDir}/../nitrogen/generated/android/kotlin" + ] + } + } +} diff --git a/nitrogen/generated/android/NitroTextOnLoad.cpp b/nitrogen/generated/android/NitroTextOnLoad.cpp new file mode 100644 index 0000000..a558792 --- /dev/null +++ b/nitrogen/generated/android/NitroTextOnLoad.cpp @@ -0,0 +1,50 @@ +/// +/// NitroTextOnLoad.cpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#ifndef BUILDING_NITROTEXT_WITH_GENERATED_CMAKE_PROJECT +#error NitroTextOnLoad.cpp is not being built with the autogenerated CMakeLists.txt project. Is a different CMakeLists.txt building this? +#endif + +#include "NitroTextOnLoad.hpp" + +#include +#include +#include + +#include "JHybridNitroTextSpec.hpp" +#include "JFunc_void_TextLayoutEvent.hpp" +#include "JFunc_void.hpp" +#include "views/JHybridNitroTextStateUpdater.hpp" +#include + +namespace margelo::nitro::nitrotext { + +int initialize(JavaVM* vm) { + using namespace margelo::nitro; + using namespace margelo::nitro::nitrotext; + using namespace facebook; + + return facebook::jni::initialize(vm, [] { + // Register native JNI methods + margelo::nitro::nitrotext::JHybridNitroTextSpec::registerNatives(); + margelo::nitro::nitrotext::JFunc_void_TextLayoutEvent_cxx::registerNatives(); + margelo::nitro::nitrotext::JFunc_void_cxx::registerNatives(); + margelo::nitro::nitrotext::views::JHybridNitroTextStateUpdater::registerNatives(); + + // Register Nitro Hybrid Objects + HybridObjectRegistry::registerHybridObjectConstructor( + "NitroText", + []() -> std::shared_ptr { + static DefaultConstructableObject object("com/nitrotext/HybridNitroText"); + auto instance = object.create(); + return instance->cthis()->shared(); + } + ); + }); +} + +} // namespace margelo::nitro::nitrotext diff --git a/nitrogen/generated/android/NitroTextOnLoad.hpp b/nitrogen/generated/android/NitroTextOnLoad.hpp new file mode 100644 index 0000000..762bf60 --- /dev/null +++ b/nitrogen/generated/android/NitroTextOnLoad.hpp @@ -0,0 +1,25 @@ +/// +/// NitroTextOnLoad.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#include +#include + +namespace margelo::nitro::nitrotext { + + /** + * Initializes the native (C++) part of NitroText, and autolinks all Hybrid Objects. + * Call this in your `JNI_OnLoad` function (probably inside `cpp-adapter.cpp`). + * Example: + * ```cpp (cpp-adapter.cpp) + * JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) { + * return margelo::nitro::nitrotext::initialize(vm); + * } + * ``` + */ + int initialize(JavaVM* vm); + +} // namespace margelo::nitro::nitrotext diff --git a/nitrogen/generated/android/c++/JDynamicTypeRamp.hpp b/nitrogen/generated/android/c++/JDynamicTypeRamp.hpp new file mode 100644 index 0000000..abbf037 --- /dev/null +++ b/nitrogen/generated/android/c++/JDynamicTypeRamp.hpp @@ -0,0 +1,86 @@ +/// +/// JDynamicTypeRamp.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#pragma once + +#include +#include "DynamicTypeRamp.hpp" + +namespace margelo::nitro::nitrotext { + + using namespace facebook; + + /** + * The C++ JNI bridge between the C++ enum "DynamicTypeRamp" and the the Kotlin enum "DynamicTypeRamp". + */ + struct JDynamicTypeRamp final: public jni::JavaClass { + public: + static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/nitrotext/DynamicTypeRamp;"; + + public: + /** + * Convert this Java/Kotlin-based enum to the C++ enum DynamicTypeRamp. + */ + [[maybe_unused]] + [[nodiscard]] + DynamicTypeRamp toCpp() const { + static const auto clazz = javaClassStatic(); + static const auto fieldOrdinal = clazz->getField("value"); + int ordinal = this->getFieldValue(fieldOrdinal); + return static_cast(ordinal); + } + + public: + /** + * Create a Java/Kotlin-based enum with the given C++ enum's value. + */ + [[maybe_unused]] + static jni::alias_ref fromCpp(DynamicTypeRamp value) { + static const auto clazz = javaClassStatic(); + static const auto fieldCAPTION2 = clazz->getStaticField("CAPTION2"); + static const auto fieldCAPTION1 = clazz->getStaticField("CAPTION1"); + static const auto fieldFOOTNOTE = clazz->getStaticField("FOOTNOTE"); + static const auto fieldSUBHEADLINE = clazz->getStaticField("SUBHEADLINE"); + static const auto fieldCALLOUT = clazz->getStaticField("CALLOUT"); + static const auto fieldBODY = clazz->getStaticField("BODY"); + static const auto fieldHEADLINE = clazz->getStaticField("HEADLINE"); + static const auto fieldTITLE3 = clazz->getStaticField("TITLE3"); + static const auto fieldTITLE2 = clazz->getStaticField("TITLE2"); + static const auto fieldTITLE1 = clazz->getStaticField("TITLE1"); + static const auto fieldLARGETITLE = clazz->getStaticField("LARGETITLE"); + + switch (value) { + case DynamicTypeRamp::CAPTION2: + return clazz->getStaticFieldValue(fieldCAPTION2); + case DynamicTypeRamp::CAPTION1: + return clazz->getStaticFieldValue(fieldCAPTION1); + case DynamicTypeRamp::FOOTNOTE: + return clazz->getStaticFieldValue(fieldFOOTNOTE); + case DynamicTypeRamp::SUBHEADLINE: + return clazz->getStaticFieldValue(fieldSUBHEADLINE); + case DynamicTypeRamp::CALLOUT: + return clazz->getStaticFieldValue(fieldCALLOUT); + case DynamicTypeRamp::BODY: + return clazz->getStaticFieldValue(fieldBODY); + case DynamicTypeRamp::HEADLINE: + return clazz->getStaticFieldValue(fieldHEADLINE); + case DynamicTypeRamp::TITLE3: + return clazz->getStaticFieldValue(fieldTITLE3); + case DynamicTypeRamp::TITLE2: + return clazz->getStaticFieldValue(fieldTITLE2); + case DynamicTypeRamp::TITLE1: + return clazz->getStaticFieldValue(fieldTITLE1); + case DynamicTypeRamp::LARGETITLE: + return clazz->getStaticFieldValue(fieldLARGETITLE); + default: + std::string stringValue = std::to_string(static_cast(value)); + throw std::invalid_argument("Invalid enum value (" + stringValue + "!"); + } + } + }; + +} // namespace margelo::nitro::nitrotext diff --git a/nitrogen/generated/android/c++/JEllipsizeMode.hpp b/nitrogen/generated/android/c++/JEllipsizeMode.hpp new file mode 100644 index 0000000..91ad21c --- /dev/null +++ b/nitrogen/generated/android/c++/JEllipsizeMode.hpp @@ -0,0 +1,65 @@ +/// +/// JEllipsizeMode.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#pragma once + +#include +#include "EllipsizeMode.hpp" + +namespace margelo::nitro::nitrotext { + + using namespace facebook; + + /** + * The C++ JNI bridge between the C++ enum "EllipsizeMode" and the the Kotlin enum "EllipsizeMode". + */ + struct JEllipsizeMode final: public jni::JavaClass { + public: + static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/nitrotext/EllipsizeMode;"; + + public: + /** + * Convert this Java/Kotlin-based enum to the C++ enum EllipsizeMode. + */ + [[maybe_unused]] + [[nodiscard]] + EllipsizeMode toCpp() const { + static const auto clazz = javaClassStatic(); + static const auto fieldOrdinal = clazz->getField("value"); + int ordinal = this->getFieldValue(fieldOrdinal); + return static_cast(ordinal); + } + + public: + /** + * Create a Java/Kotlin-based enum with the given C++ enum's value. + */ + [[maybe_unused]] + static jni::alias_ref fromCpp(EllipsizeMode value) { + static const auto clazz = javaClassStatic(); + static const auto fieldHEAD = clazz->getStaticField("HEAD"); + static const auto fieldMIDDLE = clazz->getStaticField("MIDDLE"); + static const auto fieldTAIL = clazz->getStaticField("TAIL"); + static const auto fieldCLIP = clazz->getStaticField("CLIP"); + + switch (value) { + case EllipsizeMode::HEAD: + return clazz->getStaticFieldValue(fieldHEAD); + case EllipsizeMode::MIDDLE: + return clazz->getStaticFieldValue(fieldMIDDLE); + case EllipsizeMode::TAIL: + return clazz->getStaticFieldValue(fieldTAIL); + case EllipsizeMode::CLIP: + return clazz->getStaticFieldValue(fieldCLIP); + default: + std::string stringValue = std::to_string(static_cast(value)); + throw std::invalid_argument("Invalid enum value (" + stringValue + "!"); + } + } + }; + +} // namespace margelo::nitro::nitrotext diff --git a/nitrogen/generated/android/c++/JFontStyle.hpp b/nitrogen/generated/android/c++/JFontStyle.hpp new file mode 100644 index 0000000..5d4a27f --- /dev/null +++ b/nitrogen/generated/android/c++/JFontStyle.hpp @@ -0,0 +1,62 @@ +/// +/// JFontStyle.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#pragma once + +#include +#include "FontStyle.hpp" + +namespace margelo::nitro::nitrotext { + + using namespace facebook; + + /** + * The C++ JNI bridge between the C++ enum "FontStyle" and the the Kotlin enum "FontStyle". + */ + struct JFontStyle final: public jni::JavaClass { + public: + static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/nitrotext/FontStyle;"; + + public: + /** + * Convert this Java/Kotlin-based enum to the C++ enum FontStyle. + */ + [[maybe_unused]] + [[nodiscard]] + FontStyle toCpp() const { + static const auto clazz = javaClassStatic(); + static const auto fieldOrdinal = clazz->getField("value"); + int ordinal = this->getFieldValue(fieldOrdinal); + return static_cast(ordinal); + } + + public: + /** + * Create a Java/Kotlin-based enum with the given C++ enum's value. + */ + [[maybe_unused]] + static jni::alias_ref fromCpp(FontStyle value) { + static const auto clazz = javaClassStatic(); + static const auto fieldNORMAL = clazz->getStaticField("NORMAL"); + static const auto fieldITALIC = clazz->getStaticField("ITALIC"); + static const auto fieldOBLIQUE = clazz->getStaticField("OBLIQUE"); + + switch (value) { + case FontStyle::NORMAL: + return clazz->getStaticFieldValue(fieldNORMAL); + case FontStyle::ITALIC: + return clazz->getStaticFieldValue(fieldITALIC); + case FontStyle::OBLIQUE: + return clazz->getStaticFieldValue(fieldOBLIQUE); + default: + std::string stringValue = std::to_string(static_cast(value)); + throw std::invalid_argument("Invalid enum value (" + stringValue + "!"); + } + } + }; + +} // namespace margelo::nitro::nitrotext diff --git a/nitrogen/generated/android/c++/JFontWeight.hpp b/nitrogen/generated/android/c++/JFontWeight.hpp new file mode 100644 index 0000000..a8c6440 --- /dev/null +++ b/nitrogen/generated/android/c++/JFontWeight.hpp @@ -0,0 +1,89 @@ +/// +/// JFontWeight.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#pragma once + +#include +#include "FontWeight.hpp" + +namespace margelo::nitro::nitrotext { + + using namespace facebook; + + /** + * The C++ JNI bridge between the C++ enum "FontWeight" and the the Kotlin enum "FontWeight". + */ + struct JFontWeight final: public jni::JavaClass { + public: + static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/nitrotext/FontWeight;"; + + public: + /** + * Convert this Java/Kotlin-based enum to the C++ enum FontWeight. + */ + [[maybe_unused]] + [[nodiscard]] + FontWeight toCpp() const { + static const auto clazz = javaClassStatic(); + static const auto fieldOrdinal = clazz->getField("value"); + int ordinal = this->getFieldValue(fieldOrdinal); + return static_cast(ordinal); + } + + public: + /** + * Create a Java/Kotlin-based enum with the given C++ enum's value. + */ + [[maybe_unused]] + static jni::alias_ref fromCpp(FontWeight value) { + static const auto clazz = javaClassStatic(); + static const auto fieldNORMAL = clazz->getStaticField("NORMAL"); + static const auto fieldBOLD = clazz->getStaticField("BOLD"); + static const auto fieldULTRALIGHT = clazz->getStaticField("ULTRALIGHT"); + static const auto fieldTHIN = clazz->getStaticField("THIN"); + static const auto fieldLIGHT = clazz->getStaticField("LIGHT"); + static const auto fieldMEDIUM = clazz->getStaticField("MEDIUM"); + static const auto fieldREGULAR = clazz->getStaticField("REGULAR"); + static const auto fieldSEMIBOLD = clazz->getStaticField("SEMIBOLD"); + static const auto fieldCONDENSEDBOLD = clazz->getStaticField("CONDENSEDBOLD"); + static const auto fieldCONDENSED = clazz->getStaticField("CONDENSED"); + static const auto fieldHEAVY = clazz->getStaticField("HEAVY"); + static const auto fieldBLACK = clazz->getStaticField("BLACK"); + + switch (value) { + case FontWeight::NORMAL: + return clazz->getStaticFieldValue(fieldNORMAL); + case FontWeight::BOLD: + return clazz->getStaticFieldValue(fieldBOLD); + case FontWeight::ULTRALIGHT: + return clazz->getStaticFieldValue(fieldULTRALIGHT); + case FontWeight::THIN: + return clazz->getStaticFieldValue(fieldTHIN); + case FontWeight::LIGHT: + return clazz->getStaticFieldValue(fieldLIGHT); + case FontWeight::MEDIUM: + return clazz->getStaticFieldValue(fieldMEDIUM); + case FontWeight::REGULAR: + return clazz->getStaticFieldValue(fieldREGULAR); + case FontWeight::SEMIBOLD: + return clazz->getStaticFieldValue(fieldSEMIBOLD); + case FontWeight::CONDENSEDBOLD: + return clazz->getStaticFieldValue(fieldCONDENSEDBOLD); + case FontWeight::CONDENSED: + return clazz->getStaticFieldValue(fieldCONDENSED); + case FontWeight::HEAVY: + return clazz->getStaticFieldValue(fieldHEAVY); + case FontWeight::BLACK: + return clazz->getStaticFieldValue(fieldBLACK); + default: + std::string stringValue = std::to_string(static_cast(value)); + throw std::invalid_argument("Invalid enum value (" + stringValue + "!"); + } + } + }; + +} // namespace margelo::nitro::nitrotext diff --git a/nitrogen/generated/android/c++/JFragment.hpp b/nitrogen/generated/android/c++/JFragment.hpp new file mode 100644 index 0000000..caaee25 --- /dev/null +++ b/nitrogen/generated/android/c++/JFragment.hpp @@ -0,0 +1,122 @@ +/// +/// JFragment.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#pragma once + +#include +#include "Fragment.hpp" + +#include "FontStyle.hpp" +#include "FontWeight.hpp" +#include "JFontStyle.hpp" +#include "JFontWeight.hpp" +#include "JTextAlign.hpp" +#include "JTextDecorationLine.hpp" +#include "JTextDecorationStyle.hpp" +#include "JTextTransform.hpp" +#include "TextAlign.hpp" +#include "TextDecorationLine.hpp" +#include "TextDecorationStyle.hpp" +#include "TextTransform.hpp" +#include +#include + +namespace margelo::nitro::nitrotext { + + using namespace facebook; + + /** + * The C++ JNI bridge between the C++ struct "Fragment" and the the Kotlin data class "Fragment". + */ + struct JFragment final: public jni::JavaClass { + public: + static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/nitrotext/Fragment;"; + + public: + /** + * Convert this Java/Kotlin-based struct to the C++ struct Fragment by copying all values to C++. + */ + [[maybe_unused]] + [[nodiscard]] + Fragment toCpp() const { + static const auto clazz = javaClassStatic(); + static const auto fieldText = clazz->getField("text"); + jni::local_ref text = this->getFieldValue(fieldText); + static const auto fieldSelectionColor = clazz->getField("selectionColor"); + jni::local_ref selectionColor = this->getFieldValue(fieldSelectionColor); + static const auto fieldFontSize = clazz->getField("fontSize"); + jni::local_ref fontSize = this->getFieldValue(fieldFontSize); + static const auto fieldFontWeight = clazz->getField("fontWeight"); + jni::local_ref fontWeight = this->getFieldValue(fieldFontWeight); + static const auto fieldFontColor = clazz->getField("fontColor"); + jni::local_ref fontColor = this->getFieldValue(fieldFontColor); + static const auto fieldFragmentBackgroundColor = clazz->getField("fragmentBackgroundColor"); + jni::local_ref fragmentBackgroundColor = this->getFieldValue(fieldFragmentBackgroundColor); + static const auto fieldFontStyle = clazz->getField("fontStyle"); + jni::local_ref fontStyle = this->getFieldValue(fieldFontStyle); + static const auto fieldFontFamily = clazz->getField("fontFamily"); + jni::local_ref fontFamily = this->getFieldValue(fieldFontFamily); + static const auto fieldLineHeight = clazz->getField("lineHeight"); + jni::local_ref lineHeight = this->getFieldValue(fieldLineHeight); + static const auto fieldLetterSpacing = clazz->getField("letterSpacing"); + jni::local_ref letterSpacing = this->getFieldValue(fieldLetterSpacing); + static const auto fieldTextAlign = clazz->getField("textAlign"); + jni::local_ref textAlign = this->getFieldValue(fieldTextAlign); + static const auto fieldTextTransform = clazz->getField("textTransform"); + jni::local_ref textTransform = this->getFieldValue(fieldTextTransform); + static const auto fieldTextDecorationLine = clazz->getField("textDecorationLine"); + jni::local_ref textDecorationLine = this->getFieldValue(fieldTextDecorationLine); + static const auto fieldTextDecorationColor = clazz->getField("textDecorationColor"); + jni::local_ref textDecorationColor = this->getFieldValue(fieldTextDecorationColor); + static const auto fieldTextDecorationStyle = clazz->getField("textDecorationStyle"); + jni::local_ref textDecorationStyle = this->getFieldValue(fieldTextDecorationStyle); + return Fragment( + text != nullptr ? std::make_optional(text->toStdString()) : std::nullopt, + selectionColor != nullptr ? std::make_optional(selectionColor->toStdString()) : std::nullopt, + fontSize != nullptr ? std::make_optional(fontSize->value()) : std::nullopt, + fontWeight != nullptr ? std::make_optional(fontWeight->toCpp()) : std::nullopt, + fontColor != nullptr ? std::make_optional(fontColor->toStdString()) : std::nullopt, + fragmentBackgroundColor != nullptr ? std::make_optional(fragmentBackgroundColor->toStdString()) : std::nullopt, + fontStyle != nullptr ? std::make_optional(fontStyle->toCpp()) : std::nullopt, + fontFamily != nullptr ? std::make_optional(fontFamily->toStdString()) : std::nullopt, + lineHeight != nullptr ? std::make_optional(lineHeight->value()) : std::nullopt, + letterSpacing != nullptr ? std::make_optional(letterSpacing->value()) : std::nullopt, + textAlign != nullptr ? std::make_optional(textAlign->toCpp()) : std::nullopt, + textTransform != nullptr ? std::make_optional(textTransform->toCpp()) : std::nullopt, + textDecorationLine != nullptr ? std::make_optional(textDecorationLine->toCpp()) : std::nullopt, + textDecorationColor != nullptr ? std::make_optional(textDecorationColor->toStdString()) : std::nullopt, + textDecorationStyle != nullptr ? std::make_optional(textDecorationStyle->toCpp()) : std::nullopt + ); + } + + public: + /** + * Create a Java/Kotlin-based struct by copying all values from the given C++ struct to Java. + */ + [[maybe_unused]] + static jni::local_ref fromCpp(const Fragment& value) { + return newInstance( + value.text.has_value() ? jni::make_jstring(value.text.value()) : nullptr, + value.selectionColor.has_value() ? jni::make_jstring(value.selectionColor.value()) : nullptr, + value.fontSize.has_value() ? jni::JDouble::valueOf(value.fontSize.value()) : nullptr, + value.fontWeight.has_value() ? JFontWeight::fromCpp(value.fontWeight.value()) : nullptr, + value.fontColor.has_value() ? jni::make_jstring(value.fontColor.value()) : nullptr, + value.fragmentBackgroundColor.has_value() ? jni::make_jstring(value.fragmentBackgroundColor.value()) : nullptr, + value.fontStyle.has_value() ? JFontStyle::fromCpp(value.fontStyle.value()) : nullptr, + value.fontFamily.has_value() ? jni::make_jstring(value.fontFamily.value()) : nullptr, + value.lineHeight.has_value() ? jni::JDouble::valueOf(value.lineHeight.value()) : nullptr, + value.letterSpacing.has_value() ? jni::JDouble::valueOf(value.letterSpacing.value()) : nullptr, + value.textAlign.has_value() ? JTextAlign::fromCpp(value.textAlign.value()) : nullptr, + value.textTransform.has_value() ? JTextTransform::fromCpp(value.textTransform.value()) : nullptr, + value.textDecorationLine.has_value() ? JTextDecorationLine::fromCpp(value.textDecorationLine.value()) : nullptr, + value.textDecorationColor.has_value() ? jni::make_jstring(value.textDecorationColor.value()) : nullptr, + value.textDecorationStyle.has_value() ? JTextDecorationStyle::fromCpp(value.textDecorationStyle.value()) : nullptr + ); + } + }; + +} // namespace margelo::nitro::nitrotext diff --git a/nitrogen/generated/android/c++/JFunc_void.hpp b/nitrogen/generated/android/c++/JFunc_void.hpp new file mode 100644 index 0000000..fab8e71 --- /dev/null +++ b/nitrogen/generated/android/c++/JFunc_void.hpp @@ -0,0 +1,74 @@ +/// +/// JFunc_void.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#pragma once + +#include +#include + +#include + +namespace margelo::nitro::nitrotext { + + using namespace facebook; + + /** + * Represents the Java/Kotlin callback `() -> Unit`. + * This can be passed around between C++ and Java/Kotlin. + */ + struct JFunc_void: public jni::JavaClass { + public: + static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/nitrotext/Func_void;"; + + public: + /** + * Invokes the function this `JFunc_void` instance holds through JNI. + */ + void invoke() const { + static const auto method = javaClassStatic()->getMethod("invoke"); + method(self()); + } + }; + + /** + * An implementation of Func_void that is backed by a C++ implementation (using `std::function<...>`) + */ + struct JFunc_void_cxx final: public jni::HybridClass { + public: + static jni::local_ref fromCpp(const std::function& func) { + return JFunc_void_cxx::newObjectCxxArgs(func); + } + + public: + /** + * Invokes the C++ `std::function<...>` this `JFunc_void_cxx` instance holds. + */ + void invoke_cxx() { + _func(); + } + + public: + [[nodiscard]] + inline const std::function& getFunction() const { + return _func; + } + + public: + static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/nitrotext/Func_void_cxx;"; + static void registerNatives() { + registerHybrid({makeNativeMethod("invoke_cxx", JFunc_void_cxx::invoke_cxx)}); + } + + private: + explicit JFunc_void_cxx(const std::function& func): _func(func) { } + + private: + friend HybridBase; + std::function _func; + }; + +} // namespace margelo::nitro::nitrotext diff --git a/nitrogen/generated/android/c++/JFunc_void_TextLayoutEvent.hpp b/nitrogen/generated/android/c++/JFunc_void_TextLayoutEvent.hpp new file mode 100644 index 0000000..c1ceac9 --- /dev/null +++ b/nitrogen/generated/android/c++/JFunc_void_TextLayoutEvent.hpp @@ -0,0 +1,80 @@ +/// +/// JFunc_void_TextLayoutEvent.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#pragma once + +#include +#include + +#include "TextLayoutEvent.hpp" +#include +#include "JTextLayoutEvent.hpp" +#include "TextLayout.hpp" +#include +#include "JTextLayout.hpp" +#include + +namespace margelo::nitro::nitrotext { + + using namespace facebook; + + /** + * Represents the Java/Kotlin callback `(layout: TextLayoutEvent) -> Unit`. + * This can be passed around between C++ and Java/Kotlin. + */ + struct JFunc_void_TextLayoutEvent: public jni::JavaClass { + public: + static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/nitrotext/Func_void_TextLayoutEvent;"; + + public: + /** + * Invokes the function this `JFunc_void_TextLayoutEvent` instance holds through JNI. + */ + void invoke(const TextLayoutEvent& layout) const { + static const auto method = javaClassStatic()->getMethod /* layout */)>("invoke"); + method(self(), JTextLayoutEvent::fromCpp(layout)); + } + }; + + /** + * An implementation of Func_void_TextLayoutEvent that is backed by a C++ implementation (using `std::function<...>`) + */ + struct JFunc_void_TextLayoutEvent_cxx final: public jni::HybridClass { + public: + static jni::local_ref fromCpp(const std::function& func) { + return JFunc_void_TextLayoutEvent_cxx::newObjectCxxArgs(func); + } + + public: + /** + * Invokes the C++ `std::function<...>` this `JFunc_void_TextLayoutEvent_cxx` instance holds. + */ + void invoke_cxx(jni::alias_ref layout) { + _func(layout->toCpp()); + } + + public: + [[nodiscard]] + inline const std::function& getFunction() const { + return _func; + } + + public: + static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/nitrotext/Func_void_TextLayoutEvent_cxx;"; + static void registerNatives() { + registerHybrid({makeNativeMethod("invoke_cxx", JFunc_void_TextLayoutEvent_cxx::invoke_cxx)}); + } + + private: + explicit JFunc_void_TextLayoutEvent_cxx(const std::function& func): _func(func) { } + + private: + friend HybridBase; + std::function _func; + }; + +} // namespace margelo::nitro::nitrotext diff --git a/nitrogen/generated/android/c++/JHybridNitroTextSpec.cpp b/nitrogen/generated/android/c++/JHybridNitroTextSpec.cpp new file mode 100644 index 0000000..9e036bb --- /dev/null +++ b/nitrogen/generated/android/c++/JHybridNitroTextSpec.cpp @@ -0,0 +1,411 @@ +/// +/// JHybridNitroTextSpec.cpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#include "JHybridNitroTextSpec.hpp" + +// Forward declaration of `Fragment` to properly resolve imports. +namespace margelo::nitro::nitrotext { struct Fragment; } +// Forward declaration of `FontWeight` to properly resolve imports. +namespace margelo::nitro::nitrotext { enum class FontWeight; } +// Forward declaration of `FontStyle` to properly resolve imports. +namespace margelo::nitro::nitrotext { enum class FontStyle; } +// Forward declaration of `TextAlign` to properly resolve imports. +namespace margelo::nitro::nitrotext { enum class TextAlign; } +// Forward declaration of `TextTransform` to properly resolve imports. +namespace margelo::nitro::nitrotext { enum class TextTransform; } +// Forward declaration of `TextDecorationLine` to properly resolve imports. +namespace margelo::nitro::nitrotext { enum class TextDecorationLine; } +// Forward declaration of `TextDecorationStyle` to properly resolve imports. +namespace margelo::nitro::nitrotext { enum class TextDecorationStyle; } +// Forward declaration of `EllipsizeMode` to properly resolve imports. +namespace margelo::nitro::nitrotext { enum class EllipsizeMode; } +// Forward declaration of `LineBreakStrategyIOS` to properly resolve imports. +namespace margelo::nitro::nitrotext { enum class LineBreakStrategyIOS; } +// Forward declaration of `DynamicTypeRamp` to properly resolve imports. +namespace margelo::nitro::nitrotext { enum class DynamicTypeRamp; } +// Forward declaration of `TextLayoutEvent` to properly resolve imports. +namespace margelo::nitro::nitrotext { struct TextLayoutEvent; } +// Forward declaration of `TextLayout` to properly resolve imports. +namespace margelo::nitro::nitrotext { struct TextLayout; } + +#include "Fragment.hpp" +#include +#include +#include "JFragment.hpp" +#include +#include "FontWeight.hpp" +#include "JFontWeight.hpp" +#include "FontStyle.hpp" +#include "JFontStyle.hpp" +#include "TextAlign.hpp" +#include "JTextAlign.hpp" +#include "TextTransform.hpp" +#include "JTextTransform.hpp" +#include "TextDecorationLine.hpp" +#include "JTextDecorationLine.hpp" +#include "TextDecorationStyle.hpp" +#include "JTextDecorationStyle.hpp" +#include "EllipsizeMode.hpp" +#include "JEllipsizeMode.hpp" +#include "LineBreakStrategyIOS.hpp" +#include "JLineBreakStrategyIOS.hpp" +#include "DynamicTypeRamp.hpp" +#include "JDynamicTypeRamp.hpp" +#include "TextLayoutEvent.hpp" +#include +#include "JFunc_void_TextLayoutEvent.hpp" +#include "JTextLayoutEvent.hpp" +#include "TextLayout.hpp" +#include "JTextLayout.hpp" +#include "JFunc_void.hpp" + +namespace margelo::nitro::nitrotext { + + jni::local_ref JHybridNitroTextSpec::initHybrid(jni::alias_ref jThis) { + return makeCxxInstance(jThis); + } + + void JHybridNitroTextSpec::registerNatives() { + registerHybrid({ + makeNativeMethod("initHybrid", JHybridNitroTextSpec::initHybrid), + }); + } + + size_t JHybridNitroTextSpec::getExternalMemorySize() noexcept { + static const auto method = javaClassStatic()->getMethod("getMemorySize"); + return method(_javaPart); + } + + void JHybridNitroTextSpec::dispose() noexcept { + static const auto method = javaClassStatic()->getMethod("dispose"); + method(_javaPart); + } + + // Properties + std::optional> JHybridNitroTextSpec::getFragments() { + static const auto method = javaClassStatic()->getMethod>()>("getFragments"); + auto __result = method(_javaPart); + return __result != nullptr ? std::make_optional([&]() { + size_t __size = __result->size(); + std::vector __vector; + __vector.reserve(__size); + for (size_t __i = 0; __i < __size; __i++) { + auto __element = __result->getElement(__i); + __vector.push_back(__element->toCpp()); + } + return __vector; + }()) : std::nullopt; + } + void JHybridNitroTextSpec::setFragments(const std::optional>& fragments) { + static const auto method = javaClassStatic()->getMethod> /* fragments */)>("setFragments"); + method(_javaPart, fragments.has_value() ? [&]() { + size_t __size = fragments.value().size(); + jni::local_ref> __array = jni::JArrayClass::newArray(__size); + for (size_t __i = 0; __i < __size; __i++) { + const auto& __element = fragments.value()[__i]; + __array->setElement(__i, *JFragment::fromCpp(__element)); + } + return __array; + }() : nullptr); + } + std::optional JHybridNitroTextSpec::getSelectable() { + static const auto method = javaClassStatic()->getMethod()>("getSelectable"); + auto __result = method(_javaPart); + return __result != nullptr ? std::make_optional(static_cast(__result->value())) : std::nullopt; + } + void JHybridNitroTextSpec::setSelectable(std::optional selectable) { + static const auto method = javaClassStatic()->getMethod /* selectable */)>("setSelectable"); + method(_javaPart, selectable.has_value() ? jni::JBoolean::valueOf(selectable.value()) : nullptr); + } + std::optional JHybridNitroTextSpec::getAllowFontScaling() { + static const auto method = javaClassStatic()->getMethod()>("getAllowFontScaling"); + auto __result = method(_javaPart); + return __result != nullptr ? std::make_optional(static_cast(__result->value())) : std::nullopt; + } + void JHybridNitroTextSpec::setAllowFontScaling(std::optional allowFontScaling) { + static const auto method = javaClassStatic()->getMethod /* allowFontScaling */)>("setAllowFontScaling"); + method(_javaPart, allowFontScaling.has_value() ? jni::JBoolean::valueOf(allowFontScaling.value()) : nullptr); + } + std::optional JHybridNitroTextSpec::getEllipsizeMode() { + static const auto method = javaClassStatic()->getMethod()>("getEllipsizeMode"); + auto __result = method(_javaPart); + return __result != nullptr ? std::make_optional(__result->toCpp()) : std::nullopt; + } + void JHybridNitroTextSpec::setEllipsizeMode(std::optional ellipsizeMode) { + static const auto method = javaClassStatic()->getMethod /* ellipsizeMode */)>("setEllipsizeMode"); + method(_javaPart, ellipsizeMode.has_value() ? JEllipsizeMode::fromCpp(ellipsizeMode.value()) : nullptr); + } + std::optional JHybridNitroTextSpec::getNumberOfLines() { + static const auto method = javaClassStatic()->getMethod()>("getNumberOfLines"); + auto __result = method(_javaPart); + return __result != nullptr ? std::make_optional(__result->value()) : std::nullopt; + } + void JHybridNitroTextSpec::setNumberOfLines(std::optional numberOfLines) { + static const auto method = javaClassStatic()->getMethod /* numberOfLines */)>("setNumberOfLines"); + method(_javaPart, numberOfLines.has_value() ? jni::JDouble::valueOf(numberOfLines.value()) : nullptr); + } + std::optional JHybridNitroTextSpec::getLineBreakStrategyIOS() { + static const auto method = javaClassStatic()->getMethod()>("getLineBreakStrategyIOS"); + auto __result = method(_javaPart); + return __result != nullptr ? std::make_optional(__result->toCpp()) : std::nullopt; + } + void JHybridNitroTextSpec::setLineBreakStrategyIOS(std::optional lineBreakStrategyIOS) { + static const auto method = javaClassStatic()->getMethod /* lineBreakStrategyIOS */)>("setLineBreakStrategyIOS"); + method(_javaPart, lineBreakStrategyIOS.has_value() ? JLineBreakStrategyIOS::fromCpp(lineBreakStrategyIOS.value()) : nullptr); + } + std::optional JHybridNitroTextSpec::getDynamicTypeRamp() { + static const auto method = javaClassStatic()->getMethod()>("getDynamicTypeRamp"); + auto __result = method(_javaPart); + return __result != nullptr ? std::make_optional(__result->toCpp()) : std::nullopt; + } + void JHybridNitroTextSpec::setDynamicTypeRamp(std::optional dynamicTypeRamp) { + static const auto method = javaClassStatic()->getMethod /* dynamicTypeRamp */)>("setDynamicTypeRamp"); + method(_javaPart, dynamicTypeRamp.has_value() ? JDynamicTypeRamp::fromCpp(dynamicTypeRamp.value()) : nullptr); + } + std::optional JHybridNitroTextSpec::getMaxFontSizeMultiplier() { + static const auto method = javaClassStatic()->getMethod()>("getMaxFontSizeMultiplier"); + auto __result = method(_javaPart); + return __result != nullptr ? std::make_optional(__result->value()) : std::nullopt; + } + void JHybridNitroTextSpec::setMaxFontSizeMultiplier(std::optional maxFontSizeMultiplier) { + static const auto method = javaClassStatic()->getMethod /* maxFontSizeMultiplier */)>("setMaxFontSizeMultiplier"); + method(_javaPart, maxFontSizeMultiplier.has_value() ? jni::JDouble::valueOf(maxFontSizeMultiplier.value()) : nullptr); + } + std::optional JHybridNitroTextSpec::getAdjustsFontSizeToFit() { + static const auto method = javaClassStatic()->getMethod()>("getAdjustsFontSizeToFit"); + auto __result = method(_javaPart); + return __result != nullptr ? std::make_optional(static_cast(__result->value())) : std::nullopt; + } + void JHybridNitroTextSpec::setAdjustsFontSizeToFit(std::optional adjustsFontSizeToFit) { + static const auto method = javaClassStatic()->getMethod /* adjustsFontSizeToFit */)>("setAdjustsFontSizeToFit"); + method(_javaPart, adjustsFontSizeToFit.has_value() ? jni::JBoolean::valueOf(adjustsFontSizeToFit.value()) : nullptr); + } + std::optional JHybridNitroTextSpec::getMinimumFontScale() { + static const auto method = javaClassStatic()->getMethod()>("getMinimumFontScale"); + auto __result = method(_javaPart); + return __result != nullptr ? std::make_optional(__result->value()) : std::nullopt; + } + void JHybridNitroTextSpec::setMinimumFontScale(std::optional minimumFontScale) { + static const auto method = javaClassStatic()->getMethod /* minimumFontScale */)>("setMinimumFontScale"); + method(_javaPart, minimumFontScale.has_value() ? jni::JDouble::valueOf(minimumFontScale.value()) : nullptr); + } + std::optional> JHybridNitroTextSpec::getOnTextLayout() { + static const auto method = javaClassStatic()->getMethod()>("getOnTextLayout_cxx"); + auto __result = method(_javaPart); + return __result != nullptr ? std::make_optional([&]() -> std::function { + if (__result->isInstanceOf(JFunc_void_TextLayoutEvent_cxx::javaClassStatic())) [[likely]] { + auto downcast = jni::static_ref_cast(__result); + return downcast->cthis()->getFunction(); + } else { + auto __resultRef = jni::make_global(__result); + return [__resultRef](TextLayoutEvent layout) -> void { + return __resultRef->invoke(layout); + }; + } + }()) : std::nullopt; + } + void JHybridNitroTextSpec::setOnTextLayout(const std::optional>& onTextLayout) { + static const auto method = javaClassStatic()->getMethod /* onTextLayout */)>("setOnTextLayout_cxx"); + method(_javaPart, onTextLayout.has_value() ? JFunc_void_TextLayoutEvent_cxx::fromCpp(onTextLayout.value()) : nullptr); + } + std::optional> JHybridNitroTextSpec::getOnPress() { + static const auto method = javaClassStatic()->getMethod()>("getOnPress_cxx"); + auto __result = method(_javaPart); + return __result != nullptr ? std::make_optional([&]() -> std::function { + if (__result->isInstanceOf(JFunc_void_cxx::javaClassStatic())) [[likely]] { + auto downcast = jni::static_ref_cast(__result); + return downcast->cthis()->getFunction(); + } else { + auto __resultRef = jni::make_global(__result); + return [__resultRef]() -> void { + return __resultRef->invoke(); + }; + } + }()) : std::nullopt; + } + void JHybridNitroTextSpec::setOnPress(const std::optional>& onPress) { + static const auto method = javaClassStatic()->getMethod /* onPress */)>("setOnPress_cxx"); + method(_javaPart, onPress.has_value() ? JFunc_void_cxx::fromCpp(onPress.value()) : nullptr); + } + std::optional> JHybridNitroTextSpec::getOnPressIn() { + static const auto method = javaClassStatic()->getMethod()>("getOnPressIn_cxx"); + auto __result = method(_javaPart); + return __result != nullptr ? std::make_optional([&]() -> std::function { + if (__result->isInstanceOf(JFunc_void_cxx::javaClassStatic())) [[likely]] { + auto downcast = jni::static_ref_cast(__result); + return downcast->cthis()->getFunction(); + } else { + auto __resultRef = jni::make_global(__result); + return [__resultRef]() -> void { + return __resultRef->invoke(); + }; + } + }()) : std::nullopt; + } + void JHybridNitroTextSpec::setOnPressIn(const std::optional>& onPressIn) { + static const auto method = javaClassStatic()->getMethod /* onPressIn */)>("setOnPressIn_cxx"); + method(_javaPart, onPressIn.has_value() ? JFunc_void_cxx::fromCpp(onPressIn.value()) : nullptr); + } + std::optional> JHybridNitroTextSpec::getOnPressOut() { + static const auto method = javaClassStatic()->getMethod()>("getOnPressOut_cxx"); + auto __result = method(_javaPart); + return __result != nullptr ? std::make_optional([&]() -> std::function { + if (__result->isInstanceOf(JFunc_void_cxx::javaClassStatic())) [[likely]] { + auto downcast = jni::static_ref_cast(__result); + return downcast->cthis()->getFunction(); + } else { + auto __resultRef = jni::make_global(__result); + return [__resultRef]() -> void { + return __resultRef->invoke(); + }; + } + }()) : std::nullopt; + } + void JHybridNitroTextSpec::setOnPressOut(const std::optional>& onPressOut) { + static const auto method = javaClassStatic()->getMethod /* onPressOut */)>("setOnPressOut_cxx"); + method(_javaPart, onPressOut.has_value() ? JFunc_void_cxx::fromCpp(onPressOut.value()) : nullptr); + } + std::optional JHybridNitroTextSpec::getText() { + static const auto method = javaClassStatic()->getMethod()>("getText"); + auto __result = method(_javaPart); + return __result != nullptr ? std::make_optional(__result->toStdString()) : std::nullopt; + } + void JHybridNitroTextSpec::setText(const std::optional& text) { + static const auto method = javaClassStatic()->getMethod /* text */)>("setText"); + method(_javaPart, text.has_value() ? jni::make_jstring(text.value()) : nullptr); + } + std::optional JHybridNitroTextSpec::getSelectionColor() { + static const auto method = javaClassStatic()->getMethod()>("getSelectionColor"); + auto __result = method(_javaPart); + return __result != nullptr ? std::make_optional(__result->toStdString()) : std::nullopt; + } + void JHybridNitroTextSpec::setSelectionColor(const std::optional& selectionColor) { + static const auto method = javaClassStatic()->getMethod /* selectionColor */)>("setSelectionColor"); + method(_javaPart, selectionColor.has_value() ? jni::make_jstring(selectionColor.value()) : nullptr); + } + std::optional JHybridNitroTextSpec::getFontSize() { + static const auto method = javaClassStatic()->getMethod()>("getFontSize"); + auto __result = method(_javaPart); + return __result != nullptr ? std::make_optional(__result->value()) : std::nullopt; + } + void JHybridNitroTextSpec::setFontSize(std::optional fontSize) { + static const auto method = javaClassStatic()->getMethod /* fontSize */)>("setFontSize"); + method(_javaPart, fontSize.has_value() ? jni::JDouble::valueOf(fontSize.value()) : nullptr); + } + std::optional JHybridNitroTextSpec::getFontWeight() { + static const auto method = javaClassStatic()->getMethod()>("getFontWeight"); + auto __result = method(_javaPart); + return __result != nullptr ? std::make_optional(__result->toCpp()) : std::nullopt; + } + void JHybridNitroTextSpec::setFontWeight(std::optional fontWeight) { + static const auto method = javaClassStatic()->getMethod /* fontWeight */)>("setFontWeight"); + method(_javaPart, fontWeight.has_value() ? JFontWeight::fromCpp(fontWeight.value()) : nullptr); + } + std::optional JHybridNitroTextSpec::getFontColor() { + static const auto method = javaClassStatic()->getMethod()>("getFontColor"); + auto __result = method(_javaPart); + return __result != nullptr ? std::make_optional(__result->toStdString()) : std::nullopt; + } + void JHybridNitroTextSpec::setFontColor(const std::optional& fontColor) { + static const auto method = javaClassStatic()->getMethod /* fontColor */)>("setFontColor"); + method(_javaPart, fontColor.has_value() ? jni::make_jstring(fontColor.value()) : nullptr); + } + std::optional JHybridNitroTextSpec::getFragmentBackgroundColor() { + static const auto method = javaClassStatic()->getMethod()>("getFragmentBackgroundColor"); + auto __result = method(_javaPart); + return __result != nullptr ? std::make_optional(__result->toStdString()) : std::nullopt; + } + void JHybridNitroTextSpec::setFragmentBackgroundColor(const std::optional& fragmentBackgroundColor) { + static const auto method = javaClassStatic()->getMethod /* fragmentBackgroundColor */)>("setFragmentBackgroundColor"); + method(_javaPart, fragmentBackgroundColor.has_value() ? jni::make_jstring(fragmentBackgroundColor.value()) : nullptr); + } + std::optional JHybridNitroTextSpec::getFontStyle() { + static const auto method = javaClassStatic()->getMethod()>("getFontStyle"); + auto __result = method(_javaPart); + return __result != nullptr ? std::make_optional(__result->toCpp()) : std::nullopt; + } + void JHybridNitroTextSpec::setFontStyle(std::optional fontStyle) { + static const auto method = javaClassStatic()->getMethod /* fontStyle */)>("setFontStyle"); + method(_javaPart, fontStyle.has_value() ? JFontStyle::fromCpp(fontStyle.value()) : nullptr); + } + std::optional JHybridNitroTextSpec::getFontFamily() { + static const auto method = javaClassStatic()->getMethod()>("getFontFamily"); + auto __result = method(_javaPart); + return __result != nullptr ? std::make_optional(__result->toStdString()) : std::nullopt; + } + void JHybridNitroTextSpec::setFontFamily(const std::optional& fontFamily) { + static const auto method = javaClassStatic()->getMethod /* fontFamily */)>("setFontFamily"); + method(_javaPart, fontFamily.has_value() ? jni::make_jstring(fontFamily.value()) : nullptr); + } + std::optional JHybridNitroTextSpec::getLineHeight() { + static const auto method = javaClassStatic()->getMethod()>("getLineHeight"); + auto __result = method(_javaPart); + return __result != nullptr ? std::make_optional(__result->value()) : std::nullopt; + } + void JHybridNitroTextSpec::setLineHeight(std::optional lineHeight) { + static const auto method = javaClassStatic()->getMethod /* lineHeight */)>("setLineHeight"); + method(_javaPart, lineHeight.has_value() ? jni::JDouble::valueOf(lineHeight.value()) : nullptr); + } + std::optional JHybridNitroTextSpec::getLetterSpacing() { + static const auto method = javaClassStatic()->getMethod()>("getLetterSpacing"); + auto __result = method(_javaPart); + return __result != nullptr ? std::make_optional(__result->value()) : std::nullopt; + } + void JHybridNitroTextSpec::setLetterSpacing(std::optional letterSpacing) { + static const auto method = javaClassStatic()->getMethod /* letterSpacing */)>("setLetterSpacing"); + method(_javaPart, letterSpacing.has_value() ? jni::JDouble::valueOf(letterSpacing.value()) : nullptr); + } + std::optional JHybridNitroTextSpec::getTextAlign() { + static const auto method = javaClassStatic()->getMethod()>("getTextAlign"); + auto __result = method(_javaPart); + return __result != nullptr ? std::make_optional(__result->toCpp()) : std::nullopt; + } + void JHybridNitroTextSpec::setTextAlign(std::optional textAlign) { + static const auto method = javaClassStatic()->getMethod /* textAlign */)>("setTextAlign"); + method(_javaPart, textAlign.has_value() ? JTextAlign::fromCpp(textAlign.value()) : nullptr); + } + std::optional JHybridNitroTextSpec::getTextTransform() { + static const auto method = javaClassStatic()->getMethod()>("getTextTransform"); + auto __result = method(_javaPart); + return __result != nullptr ? std::make_optional(__result->toCpp()) : std::nullopt; + } + void JHybridNitroTextSpec::setTextTransform(std::optional textTransform) { + static const auto method = javaClassStatic()->getMethod /* textTransform */)>("setTextTransform"); + method(_javaPart, textTransform.has_value() ? JTextTransform::fromCpp(textTransform.value()) : nullptr); + } + std::optional JHybridNitroTextSpec::getTextDecorationLine() { + static const auto method = javaClassStatic()->getMethod()>("getTextDecorationLine"); + auto __result = method(_javaPart); + return __result != nullptr ? std::make_optional(__result->toCpp()) : std::nullopt; + } + void JHybridNitroTextSpec::setTextDecorationLine(std::optional textDecorationLine) { + static const auto method = javaClassStatic()->getMethod /* textDecorationLine */)>("setTextDecorationLine"); + method(_javaPart, textDecorationLine.has_value() ? JTextDecorationLine::fromCpp(textDecorationLine.value()) : nullptr); + } + std::optional JHybridNitroTextSpec::getTextDecorationColor() { + static const auto method = javaClassStatic()->getMethod()>("getTextDecorationColor"); + auto __result = method(_javaPart); + return __result != nullptr ? std::make_optional(__result->toStdString()) : std::nullopt; + } + void JHybridNitroTextSpec::setTextDecorationColor(const std::optional& textDecorationColor) { + static const auto method = javaClassStatic()->getMethod /* textDecorationColor */)>("setTextDecorationColor"); + method(_javaPart, textDecorationColor.has_value() ? jni::make_jstring(textDecorationColor.value()) : nullptr); + } + std::optional JHybridNitroTextSpec::getTextDecorationStyle() { + static const auto method = javaClassStatic()->getMethod()>("getTextDecorationStyle"); + auto __result = method(_javaPart); + return __result != nullptr ? std::make_optional(__result->toCpp()) : std::nullopt; + } + void JHybridNitroTextSpec::setTextDecorationStyle(std::optional textDecorationStyle) { + static const auto method = javaClassStatic()->getMethod /* textDecorationStyle */)>("setTextDecorationStyle"); + method(_javaPart, textDecorationStyle.has_value() ? JTextDecorationStyle::fromCpp(textDecorationStyle.value()) : nullptr); + } + + // Methods + + +} // namespace margelo::nitro::nitrotext diff --git a/nitrogen/generated/android/c++/JHybridNitroTextSpec.hpp b/nitrogen/generated/android/c++/JHybridNitroTextSpec.hpp new file mode 100644 index 0000000..6c2a518 --- /dev/null +++ b/nitrogen/generated/android/c++/JHybridNitroTextSpec.hpp @@ -0,0 +1,121 @@ +/// +/// HybridNitroTextSpec.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#pragma once + +#include +#include +#include "HybridNitroTextSpec.hpp" + + + + +namespace margelo::nitro::nitrotext { + + using namespace facebook; + + class JHybridNitroTextSpec: public jni::HybridClass, + public virtual HybridNitroTextSpec { + public: + static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/nitrotext/HybridNitroTextSpec;"; + static jni::local_ref initHybrid(jni::alias_ref jThis); + static void registerNatives(); + + protected: + // C++ constructor (called from Java via `initHybrid()`) + explicit JHybridNitroTextSpec(jni::alias_ref jThis) : + HybridObject(HybridNitroTextSpec::TAG), + HybridBase(jThis), + _javaPart(jni::make_global(jThis)) {} + + public: + ~JHybridNitroTextSpec() override { + // Hermes GC can destroy JS objects on a non-JNI Thread. + jni::ThreadScope::WithClassLoader([&] { _javaPart.reset(); }); + } + + public: + size_t getExternalMemorySize() noexcept override; + void dispose() noexcept override; + + public: + inline const jni::global_ref& getJavaPart() const noexcept { + return _javaPart; + } + + public: + // Properties + std::optional> getFragments() override; + void setFragments(const std::optional>& fragments) override; + std::optional getSelectable() override; + void setSelectable(std::optional selectable) override; + std::optional getAllowFontScaling() override; + void setAllowFontScaling(std::optional allowFontScaling) override; + std::optional getEllipsizeMode() override; + void setEllipsizeMode(std::optional ellipsizeMode) override; + std::optional getNumberOfLines() override; + void setNumberOfLines(std::optional numberOfLines) override; + std::optional getLineBreakStrategyIOS() override; + void setLineBreakStrategyIOS(std::optional lineBreakStrategyIOS) override; + std::optional getDynamicTypeRamp() override; + void setDynamicTypeRamp(std::optional dynamicTypeRamp) override; + std::optional getMaxFontSizeMultiplier() override; + void setMaxFontSizeMultiplier(std::optional maxFontSizeMultiplier) override; + std::optional getAdjustsFontSizeToFit() override; + void setAdjustsFontSizeToFit(std::optional adjustsFontSizeToFit) override; + std::optional getMinimumFontScale() override; + void setMinimumFontScale(std::optional minimumFontScale) override; + std::optional> getOnTextLayout() override; + void setOnTextLayout(const std::optional>& onTextLayout) override; + std::optional> getOnPress() override; + void setOnPress(const std::optional>& onPress) override; + std::optional> getOnPressIn() override; + void setOnPressIn(const std::optional>& onPressIn) override; + std::optional> getOnPressOut() override; + void setOnPressOut(const std::optional>& onPressOut) override; + std::optional getText() override; + void setText(const std::optional& text) override; + std::optional getSelectionColor() override; + void setSelectionColor(const std::optional& selectionColor) override; + std::optional getFontSize() override; + void setFontSize(std::optional fontSize) override; + std::optional getFontWeight() override; + void setFontWeight(std::optional fontWeight) override; + std::optional getFontColor() override; + void setFontColor(const std::optional& fontColor) override; + std::optional getFragmentBackgroundColor() override; + void setFragmentBackgroundColor(const std::optional& fragmentBackgroundColor) override; + std::optional getFontStyle() override; + void setFontStyle(std::optional fontStyle) override; + std::optional getFontFamily() override; + void setFontFamily(const std::optional& fontFamily) override; + std::optional getLineHeight() override; + void setLineHeight(std::optional lineHeight) override; + std::optional getLetterSpacing() override; + void setLetterSpacing(std::optional letterSpacing) override; + std::optional getTextAlign() override; + void setTextAlign(std::optional textAlign) override; + std::optional getTextTransform() override; + void setTextTransform(std::optional textTransform) override; + std::optional getTextDecorationLine() override; + void setTextDecorationLine(std::optional textDecorationLine) override; + std::optional getTextDecorationColor() override; + void setTextDecorationColor(const std::optional& textDecorationColor) override; + std::optional getTextDecorationStyle() override; + void setTextDecorationStyle(std::optional textDecorationStyle) override; + + public: + // Methods + + + private: + friend HybridBase; + using HybridBase::HybridBase; + jni::global_ref _javaPart; + }; + +} // namespace margelo::nitro::nitrotext diff --git a/nitrogen/generated/android/c++/JLineBreakStrategyIOS.hpp b/nitrogen/generated/android/c++/JLineBreakStrategyIOS.hpp new file mode 100644 index 0000000..feafca9 --- /dev/null +++ b/nitrogen/generated/android/c++/JLineBreakStrategyIOS.hpp @@ -0,0 +1,65 @@ +/// +/// JLineBreakStrategyIOS.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#pragma once + +#include +#include "LineBreakStrategyIOS.hpp" + +namespace margelo::nitro::nitrotext { + + using namespace facebook; + + /** + * The C++ JNI bridge between the C++ enum "LineBreakStrategyIOS" and the the Kotlin enum "LineBreakStrategyIOS". + */ + struct JLineBreakStrategyIOS final: public jni::JavaClass { + public: + static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/nitrotext/LineBreakStrategyIOS;"; + + public: + /** + * Convert this Java/Kotlin-based enum to the C++ enum LineBreakStrategyIOS. + */ + [[maybe_unused]] + [[nodiscard]] + LineBreakStrategyIOS toCpp() const { + static const auto clazz = javaClassStatic(); + static const auto fieldOrdinal = clazz->getField("value"); + int ordinal = this->getFieldValue(fieldOrdinal); + return static_cast(ordinal); + } + + public: + /** + * Create a Java/Kotlin-based enum with the given C++ enum's value. + */ + [[maybe_unused]] + static jni::alias_ref fromCpp(LineBreakStrategyIOS value) { + static const auto clazz = javaClassStatic(); + static const auto fieldNONE = clazz->getStaticField("NONE"); + static const auto fieldSTANDARD = clazz->getStaticField("STANDARD"); + static const auto fieldHANGUL_WORD = clazz->getStaticField("HANGUL_WORD"); + static const auto fieldPUSH_OUT = clazz->getStaticField("PUSH_OUT"); + + switch (value) { + case LineBreakStrategyIOS::NONE: + return clazz->getStaticFieldValue(fieldNONE); + case LineBreakStrategyIOS::STANDARD: + return clazz->getStaticFieldValue(fieldSTANDARD); + case LineBreakStrategyIOS::HANGUL_WORD: + return clazz->getStaticFieldValue(fieldHANGUL_WORD); + case LineBreakStrategyIOS::PUSH_OUT: + return clazz->getStaticFieldValue(fieldPUSH_OUT); + default: + std::string stringValue = std::to_string(static_cast(value)); + throw std::invalid_argument("Invalid enum value (" + stringValue + "!"); + } + } + }; + +} // namespace margelo::nitro::nitrotext diff --git a/nitrogen/generated/android/c++/JTextAlign.hpp b/nitrogen/generated/android/c++/JTextAlign.hpp new file mode 100644 index 0000000..1bf081b --- /dev/null +++ b/nitrogen/generated/android/c++/JTextAlign.hpp @@ -0,0 +1,68 @@ +/// +/// JTextAlign.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#pragma once + +#include +#include "TextAlign.hpp" + +namespace margelo::nitro::nitrotext { + + using namespace facebook; + + /** + * The C++ JNI bridge between the C++ enum "TextAlign" and the the Kotlin enum "TextAlign". + */ + struct JTextAlign final: public jni::JavaClass { + public: + static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/nitrotext/TextAlign;"; + + public: + /** + * Convert this Java/Kotlin-based enum to the C++ enum TextAlign. + */ + [[maybe_unused]] + [[nodiscard]] + TextAlign toCpp() const { + static const auto clazz = javaClassStatic(); + static const auto fieldOrdinal = clazz->getField("value"); + int ordinal = this->getFieldValue(fieldOrdinal); + return static_cast(ordinal); + } + + public: + /** + * Create a Java/Kotlin-based enum with the given C++ enum's value. + */ + [[maybe_unused]] + static jni::alias_ref fromCpp(TextAlign value) { + static const auto clazz = javaClassStatic(); + static const auto fieldAUTO = clazz->getStaticField("AUTO"); + static const auto fieldLEFT = clazz->getStaticField("LEFT"); + static const auto fieldRIGHT = clazz->getStaticField("RIGHT"); + static const auto fieldCENTER = clazz->getStaticField("CENTER"); + static const auto fieldJUSTIFY = clazz->getStaticField("JUSTIFY"); + + switch (value) { + case TextAlign::AUTO: + return clazz->getStaticFieldValue(fieldAUTO); + case TextAlign::LEFT: + return clazz->getStaticFieldValue(fieldLEFT); + case TextAlign::RIGHT: + return clazz->getStaticFieldValue(fieldRIGHT); + case TextAlign::CENTER: + return clazz->getStaticFieldValue(fieldCENTER); + case TextAlign::JUSTIFY: + return clazz->getStaticFieldValue(fieldJUSTIFY); + default: + std::string stringValue = std::to_string(static_cast(value)); + throw std::invalid_argument("Invalid enum value (" + stringValue + "!"); + } + } + }; + +} // namespace margelo::nitro::nitrotext diff --git a/nitrogen/generated/android/c++/JTextDecorationLine.hpp b/nitrogen/generated/android/c++/JTextDecorationLine.hpp new file mode 100644 index 0000000..143cc7e --- /dev/null +++ b/nitrogen/generated/android/c++/JTextDecorationLine.hpp @@ -0,0 +1,65 @@ +/// +/// JTextDecorationLine.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#pragma once + +#include +#include "TextDecorationLine.hpp" + +namespace margelo::nitro::nitrotext { + + using namespace facebook; + + /** + * The C++ JNI bridge between the C++ enum "TextDecorationLine" and the the Kotlin enum "TextDecorationLine". + */ + struct JTextDecorationLine final: public jni::JavaClass { + public: + static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/nitrotext/TextDecorationLine;"; + + public: + /** + * Convert this Java/Kotlin-based enum to the C++ enum TextDecorationLine. + */ + [[maybe_unused]] + [[nodiscard]] + TextDecorationLine toCpp() const { + static const auto clazz = javaClassStatic(); + static const auto fieldOrdinal = clazz->getField("value"); + int ordinal = this->getFieldValue(fieldOrdinal); + return static_cast(ordinal); + } + + public: + /** + * Create a Java/Kotlin-based enum with the given C++ enum's value. + */ + [[maybe_unused]] + static jni::alias_ref fromCpp(TextDecorationLine value) { + static const auto clazz = javaClassStatic(); + static const auto fieldNONE = clazz->getStaticField("NONE"); + static const auto fieldUNDERLINE = clazz->getStaticField("UNDERLINE"); + static const auto fieldLINE_THROUGH = clazz->getStaticField("LINE_THROUGH"); + static const auto fieldUNDERLINE_LINE_THROUGH = clazz->getStaticField("UNDERLINE_LINE_THROUGH"); + + switch (value) { + case TextDecorationLine::NONE: + return clazz->getStaticFieldValue(fieldNONE); + case TextDecorationLine::UNDERLINE: + return clazz->getStaticFieldValue(fieldUNDERLINE); + case TextDecorationLine::LINE_THROUGH: + return clazz->getStaticFieldValue(fieldLINE_THROUGH); + case TextDecorationLine::UNDERLINE_LINE_THROUGH: + return clazz->getStaticFieldValue(fieldUNDERLINE_LINE_THROUGH); + default: + std::string stringValue = std::to_string(static_cast(value)); + throw std::invalid_argument("Invalid enum value (" + stringValue + "!"); + } + } + }; + +} // namespace margelo::nitro::nitrotext diff --git a/nitrogen/generated/android/c++/JTextDecorationStyle.hpp b/nitrogen/generated/android/c++/JTextDecorationStyle.hpp new file mode 100644 index 0000000..1a459c1 --- /dev/null +++ b/nitrogen/generated/android/c++/JTextDecorationStyle.hpp @@ -0,0 +1,65 @@ +/// +/// JTextDecorationStyle.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#pragma once + +#include +#include "TextDecorationStyle.hpp" + +namespace margelo::nitro::nitrotext { + + using namespace facebook; + + /** + * The C++ JNI bridge between the C++ enum "TextDecorationStyle" and the the Kotlin enum "TextDecorationStyle". + */ + struct JTextDecorationStyle final: public jni::JavaClass { + public: + static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/nitrotext/TextDecorationStyle;"; + + public: + /** + * Convert this Java/Kotlin-based enum to the C++ enum TextDecorationStyle. + */ + [[maybe_unused]] + [[nodiscard]] + TextDecorationStyle toCpp() const { + static const auto clazz = javaClassStatic(); + static const auto fieldOrdinal = clazz->getField("value"); + int ordinal = this->getFieldValue(fieldOrdinal); + return static_cast(ordinal); + } + + public: + /** + * Create a Java/Kotlin-based enum with the given C++ enum's value. + */ + [[maybe_unused]] + static jni::alias_ref fromCpp(TextDecorationStyle value) { + static const auto clazz = javaClassStatic(); + static const auto fieldSOLID = clazz->getStaticField("SOLID"); + static const auto fieldDOUBLE = clazz->getStaticField("DOUBLE"); + static const auto fieldDOTTED = clazz->getStaticField("DOTTED"); + static const auto fieldDASHED = clazz->getStaticField("DASHED"); + + switch (value) { + case TextDecorationStyle::SOLID: + return clazz->getStaticFieldValue(fieldSOLID); + case TextDecorationStyle::DOUBLE: + return clazz->getStaticFieldValue(fieldDOUBLE); + case TextDecorationStyle::DOTTED: + return clazz->getStaticFieldValue(fieldDOTTED); + case TextDecorationStyle::DASHED: + return clazz->getStaticFieldValue(fieldDASHED); + default: + std::string stringValue = std::to_string(static_cast(value)); + throw std::invalid_argument("Invalid enum value (" + stringValue + "!"); + } + } + }; + +} // namespace margelo::nitro::nitrotext diff --git a/nitrogen/generated/android/c++/JTextLayout.hpp b/nitrogen/generated/android/c++/JTextLayout.hpp new file mode 100644 index 0000000..0224ce2 --- /dev/null +++ b/nitrogen/generated/android/c++/JTextLayout.hpp @@ -0,0 +1,85 @@ +/// +/// JTextLayout.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#pragma once + +#include +#include "TextLayout.hpp" + +#include + +namespace margelo::nitro::nitrotext { + + using namespace facebook; + + /** + * The C++ JNI bridge between the C++ struct "TextLayout" and the the Kotlin data class "TextLayout". + */ + struct JTextLayout final: public jni::JavaClass { + public: + static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/nitrotext/TextLayout;"; + + public: + /** + * Convert this Java/Kotlin-based struct to the C++ struct TextLayout by copying all values to C++. + */ + [[maybe_unused]] + [[nodiscard]] + TextLayout toCpp() const { + static const auto clazz = javaClassStatic(); + static const auto fieldText = clazz->getField("text"); + jni::local_ref text = this->getFieldValue(fieldText); + static const auto fieldX = clazz->getField("x"); + double x = this->getFieldValue(fieldX); + static const auto fieldY = clazz->getField("y"); + double y = this->getFieldValue(fieldY); + static const auto fieldWidth = clazz->getField("width"); + double width = this->getFieldValue(fieldWidth); + static const auto fieldHeight = clazz->getField("height"); + double height = this->getFieldValue(fieldHeight); + static const auto fieldDescender = clazz->getField("descender"); + double descender = this->getFieldValue(fieldDescender); + static const auto fieldCapHeight = clazz->getField("capHeight"); + double capHeight = this->getFieldValue(fieldCapHeight); + static const auto fieldAscender = clazz->getField("ascender"); + double ascender = this->getFieldValue(fieldAscender); + static const auto fieldXHeight = clazz->getField("xHeight"); + double xHeight = this->getFieldValue(fieldXHeight); + return TextLayout( + text->toStdString(), + x, + y, + width, + height, + descender, + capHeight, + ascender, + xHeight + ); + } + + public: + /** + * Create a Java/Kotlin-based struct by copying all values from the given C++ struct to Java. + */ + [[maybe_unused]] + static jni::local_ref fromCpp(const TextLayout& value) { + return newInstance( + jni::make_jstring(value.text), + value.x, + value.y, + value.width, + value.height, + value.descender, + value.capHeight, + value.ascender, + value.xHeight + ); + } + }; + +} // namespace margelo::nitro::nitrotext diff --git a/nitrogen/generated/android/c++/JTextLayoutEvent.hpp b/nitrogen/generated/android/c++/JTextLayoutEvent.hpp new file mode 100644 index 0000000..feb6cf0 --- /dev/null +++ b/nitrogen/generated/android/c++/JTextLayoutEvent.hpp @@ -0,0 +1,73 @@ +/// +/// JTextLayoutEvent.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#pragma once + +#include +#include "TextLayoutEvent.hpp" + +#include "JTextLayout.hpp" +#include "TextLayout.hpp" +#include +#include + +namespace margelo::nitro::nitrotext { + + using namespace facebook; + + /** + * The C++ JNI bridge between the C++ struct "TextLayoutEvent" and the the Kotlin data class "TextLayoutEvent". + */ + struct JTextLayoutEvent final: public jni::JavaClass { + public: + static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/nitrotext/TextLayoutEvent;"; + + public: + /** + * Convert this Java/Kotlin-based struct to the C++ struct TextLayoutEvent by copying all values to C++. + */ + [[maybe_unused]] + [[nodiscard]] + TextLayoutEvent toCpp() const { + static const auto clazz = javaClassStatic(); + static const auto fieldLines = clazz->getField>("lines"); + jni::local_ref> lines = this->getFieldValue(fieldLines); + return TextLayoutEvent( + [&]() { + size_t __size = lines->size(); + std::vector __vector; + __vector.reserve(__size); + for (size_t __i = 0; __i < __size; __i++) { + auto __element = lines->getElement(__i); + __vector.push_back(__element->toCpp()); + } + return __vector; + }() + ); + } + + public: + /** + * Create a Java/Kotlin-based struct by copying all values from the given C++ struct to Java. + */ + [[maybe_unused]] + static jni::local_ref fromCpp(const TextLayoutEvent& value) { + return newInstance( + [&]() { + size_t __size = value.lines.size(); + jni::local_ref> __array = jni::JArrayClass::newArray(__size); + for (size_t __i = 0; __i < __size; __i++) { + const auto& __element = value.lines[__i]; + __array->setElement(__i, *JTextLayout::fromCpp(__element)); + } + return __array; + }() + ); + } + }; + +} // namespace margelo::nitro::nitrotext diff --git a/nitrogen/generated/android/c++/JTextTransform.hpp b/nitrogen/generated/android/c++/JTextTransform.hpp new file mode 100644 index 0000000..52b2f17 --- /dev/null +++ b/nitrogen/generated/android/c++/JTextTransform.hpp @@ -0,0 +1,65 @@ +/// +/// JTextTransform.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#pragma once + +#include +#include "TextTransform.hpp" + +namespace margelo::nitro::nitrotext { + + using namespace facebook; + + /** + * The C++ JNI bridge between the C++ enum "TextTransform" and the the Kotlin enum "TextTransform". + */ + struct JTextTransform final: public jni::JavaClass { + public: + static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/nitrotext/TextTransform;"; + + public: + /** + * Convert this Java/Kotlin-based enum to the C++ enum TextTransform. + */ + [[maybe_unused]] + [[nodiscard]] + TextTransform toCpp() const { + static const auto clazz = javaClassStatic(); + static const auto fieldOrdinal = clazz->getField("value"); + int ordinal = this->getFieldValue(fieldOrdinal); + return static_cast(ordinal); + } + + public: + /** + * Create a Java/Kotlin-based enum with the given C++ enum's value. + */ + [[maybe_unused]] + static jni::alias_ref fromCpp(TextTransform value) { + static const auto clazz = javaClassStatic(); + static const auto fieldNONE = clazz->getStaticField("NONE"); + static const auto fieldUPPERCASE = clazz->getStaticField("UPPERCASE"); + static const auto fieldLOWERCASE = clazz->getStaticField("LOWERCASE"); + static const auto fieldCAPITALIZE = clazz->getStaticField("CAPITALIZE"); + + switch (value) { + case TextTransform::NONE: + return clazz->getStaticFieldValue(fieldNONE); + case TextTransform::UPPERCASE: + return clazz->getStaticFieldValue(fieldUPPERCASE); + case TextTransform::LOWERCASE: + return clazz->getStaticFieldValue(fieldLOWERCASE); + case TextTransform::CAPITALIZE: + return clazz->getStaticFieldValue(fieldCAPITALIZE); + default: + std::string stringValue = std::to_string(static_cast(value)); + throw std::invalid_argument("Invalid enum value (" + stringValue + "!"); + } + } + }; + +} // namespace margelo::nitro::nitrotext diff --git a/nitrogen/generated/android/c++/views/JHybridNitroTextStateUpdater.cpp b/nitrogen/generated/android/c++/views/JHybridNitroTextStateUpdater.cpp new file mode 100644 index 0000000..2ce0684 --- /dev/null +++ b/nitrogen/generated/android/c++/views/JHybridNitroTextStateUpdater.cpp @@ -0,0 +1,168 @@ +/// +/// JHybridNitroTextStateUpdater.cpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#include "JHybridNitroTextStateUpdater.hpp" +#include "views/HybridNitroTextComponent.hpp" +#include + +namespace margelo::nitro::nitrotext::views { + +using namespace facebook; +using ConcreteStateData = react::ConcreteState; + +void JHybridNitroTextStateUpdater::updateViewProps(jni::alias_ref /* class */, + jni::alias_ref javaView, + jni::alias_ref stateWrapperInterface) { + JHybridNitroTextSpec* view = javaView->cthis(); + + // Get concrete StateWrapperImpl from passed StateWrapper interface object + jobject rawStateWrapper = stateWrapperInterface.get(); + if (!stateWrapperInterface->isInstanceOf(react::StateWrapperImpl::javaClassStatic())) { + throw std::runtime_error("StateWrapper is not a StateWrapperImpl"); + } + auto stateWrapper = jni::alias_ref{ + static_cast(rawStateWrapper)}; + + std::shared_ptr state = stateWrapper->cthis()->getState(); + auto concreteState = std::dynamic_pointer_cast(state); + const HybridNitroTextState& data = concreteState->getData(); + const std::optional& maybeProps = data.getProps(); + if (!maybeProps.has_value()) { + // Props aren't set yet! + throw std::runtime_error("HybridNitroTextState's data doesn't contain any props!"); + } + const HybridNitroTextProps& props = maybeProps.value(); + if (props.fragments.isDirty) { + view->setFragments(props.fragments.value); + // TODO: Set isDirty = false + } + if (props.selectable.isDirty) { + view->setSelectable(props.selectable.value); + // TODO: Set isDirty = false + } + if (props.allowFontScaling.isDirty) { + view->setAllowFontScaling(props.allowFontScaling.value); + // TODO: Set isDirty = false + } + if (props.ellipsizeMode.isDirty) { + view->setEllipsizeMode(props.ellipsizeMode.value); + // TODO: Set isDirty = false + } + if (props.numberOfLines.isDirty) { + view->setNumberOfLines(props.numberOfLines.value); + // TODO: Set isDirty = false + } + if (props.lineBreakStrategyIOS.isDirty) { + view->setLineBreakStrategyIOS(props.lineBreakStrategyIOS.value); + // TODO: Set isDirty = false + } + if (props.dynamicTypeRamp.isDirty) { + view->setDynamicTypeRamp(props.dynamicTypeRamp.value); + // TODO: Set isDirty = false + } + if (props.maxFontSizeMultiplier.isDirty) { + view->setMaxFontSizeMultiplier(props.maxFontSizeMultiplier.value); + // TODO: Set isDirty = false + } + if (props.adjustsFontSizeToFit.isDirty) { + view->setAdjustsFontSizeToFit(props.adjustsFontSizeToFit.value); + // TODO: Set isDirty = false + } + if (props.minimumFontScale.isDirty) { + view->setMinimumFontScale(props.minimumFontScale.value); + // TODO: Set isDirty = false + } + if (props.onTextLayout.isDirty) { + view->setOnTextLayout(props.onTextLayout.value); + // TODO: Set isDirty = false + } + if (props.onPress.isDirty) { + view->setOnPress(props.onPress.value); + // TODO: Set isDirty = false + } + if (props.onPressIn.isDirty) { + view->setOnPressIn(props.onPressIn.value); + // TODO: Set isDirty = false + } + if (props.onPressOut.isDirty) { + view->setOnPressOut(props.onPressOut.value); + // TODO: Set isDirty = false + } + if (props.text.isDirty) { + view->setText(props.text.value); + // TODO: Set isDirty = false + } + if (props.selectionColor.isDirty) { + view->setSelectionColor(props.selectionColor.value); + // TODO: Set isDirty = false + } + if (props.fontSize.isDirty) { + view->setFontSize(props.fontSize.value); + // TODO: Set isDirty = false + } + if (props.fontWeight.isDirty) { + view->setFontWeight(props.fontWeight.value); + // TODO: Set isDirty = false + } + if (props.fontColor.isDirty) { + view->setFontColor(props.fontColor.value); + // TODO: Set isDirty = false + } + if (props.fragmentBackgroundColor.isDirty) { + view->setFragmentBackgroundColor(props.fragmentBackgroundColor.value); + // TODO: Set isDirty = false + } + if (props.fontStyle.isDirty) { + view->setFontStyle(props.fontStyle.value); + // TODO: Set isDirty = false + } + if (props.fontFamily.isDirty) { + view->setFontFamily(props.fontFamily.value); + // TODO: Set isDirty = false + } + if (props.lineHeight.isDirty) { + view->setLineHeight(props.lineHeight.value); + // TODO: Set isDirty = false + } + if (props.letterSpacing.isDirty) { + view->setLetterSpacing(props.letterSpacing.value); + // TODO: Set isDirty = false + } + if (props.textAlign.isDirty) { + view->setTextAlign(props.textAlign.value); + // TODO: Set isDirty = false + } + if (props.textTransform.isDirty) { + view->setTextTransform(props.textTransform.value); + // TODO: Set isDirty = false + } + if (props.textDecorationLine.isDirty) { + view->setTextDecorationLine(props.textDecorationLine.value); + // TODO: Set isDirty = false + } + if (props.textDecorationColor.isDirty) { + view->setTextDecorationColor(props.textDecorationColor.value); + // TODO: Set isDirty = false + } + if (props.textDecorationStyle.isDirty) { + view->setTextDecorationStyle(props.textDecorationStyle.value); + // TODO: Set isDirty = false + } + + // Update hybridRef if it changed + if (props.hybridRef.isDirty) { + // hybridRef changed - call it with new this + const auto& maybeFunc = props.hybridRef.value; + if (maybeFunc.has_value()) { + std::shared_ptr shared = javaView->cthis()->shared_cast(); + maybeFunc.value()(shared); + } + // TODO: Set isDirty = false + } +} + +} // namespace margelo::nitro::nitrotext::views diff --git a/nitrogen/generated/android/c++/views/JHybridNitroTextStateUpdater.hpp b/nitrogen/generated/android/c++/views/JHybridNitroTextStateUpdater.hpp new file mode 100644 index 0000000..c102d50 --- /dev/null +++ b/nitrogen/generated/android/c++/views/JHybridNitroTextStateUpdater.hpp @@ -0,0 +1,49 @@ +/// +/// JHybridNitroTextStateUpdater.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#pragma once + +#ifndef RN_SERIALIZABLE_STATE +#error NitroText was compiled without the 'RN_SERIALIZABLE_STATE' flag. This flag is required for Nitro Views - set it in your CMakeLists! +#endif + +#include +#include +#include +#include +#include +#include +#include "JHybridNitroTextSpec.hpp" +#include "views/HybridNitroTextComponent.hpp" + +namespace margelo::nitro::nitrotext::views { + +using namespace facebook; + +class JHybridNitroTextStateUpdater: public jni::JavaClass { +public: + static constexpr auto kJavaDescriptor = "Lcom/margelo/nitro/nitrotext/views/HybridNitroTextStateUpdater;"; + +public: + static void updateViewProps(jni::alias_ref /* class */, + jni::alias_ref view, + jni::alias_ref stateWrapperInterface); + +public: + static void registerNatives() { + // Register JNI calls + javaClassStatic()->registerNatives({ + makeNativeMethod("updateViewProps", JHybridNitroTextStateUpdater::updateViewProps), + }); + // Register React Native view component descriptor + auto provider = react::concreteComponentDescriptorProvider(); + auto providerRegistry = react::CoreComponentsRegistry::sharedProviderRegistry(); + providerRegistry->add(provider); + } +}; + +} // namespace margelo::nitro::nitrotext::views diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/DynamicTypeRamp.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/DynamicTypeRamp.kt new file mode 100644 index 0000000..efc03bb --- /dev/null +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/DynamicTypeRamp.kt @@ -0,0 +1,30 @@ +/// +/// DynamicTypeRamp.kt +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +package com.margelo.nitro.nitrotext + +import androidx.annotation.Keep +import com.facebook.proguard.annotations.DoNotStrip + +/** + * Represents the JavaScript enum/union "DynamicTypeRamp". + */ +@DoNotStrip +@Keep +enum class DynamicTypeRamp(@DoNotStrip @Keep val value: Int) { + CAPTION2(0), + CAPTION1(1), + FOOTNOTE(2), + SUBHEADLINE(3), + CALLOUT(4), + BODY(5), + HEADLINE(6), + TITLE3(7), + TITLE2(8), + TITLE1(9), + LARGETITLE(10); +} diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/EllipsizeMode.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/EllipsizeMode.kt new file mode 100644 index 0000000..6d91a05 --- /dev/null +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/EllipsizeMode.kt @@ -0,0 +1,23 @@ +/// +/// EllipsizeMode.kt +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +package com.margelo.nitro.nitrotext + +import androidx.annotation.Keep +import com.facebook.proguard.annotations.DoNotStrip + +/** + * Represents the JavaScript enum/union "EllipsizeMode". + */ +@DoNotStrip +@Keep +enum class EllipsizeMode(@DoNotStrip @Keep val value: Int) { + HEAD(0), + MIDDLE(1), + TAIL(2), + CLIP(3); +} diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/FontStyle.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/FontStyle.kt new file mode 100644 index 0000000..a848fc2 --- /dev/null +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/FontStyle.kt @@ -0,0 +1,22 @@ +/// +/// FontStyle.kt +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +package com.margelo.nitro.nitrotext + +import androidx.annotation.Keep +import com.facebook.proguard.annotations.DoNotStrip + +/** + * Represents the JavaScript enum/union "FontStyle". + */ +@DoNotStrip +@Keep +enum class FontStyle(@DoNotStrip @Keep val value: Int) { + NORMAL(0), + ITALIC(1), + OBLIQUE(2); +} diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/FontWeight.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/FontWeight.kt new file mode 100644 index 0000000..85db768 --- /dev/null +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/FontWeight.kt @@ -0,0 +1,31 @@ +/// +/// FontWeight.kt +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +package com.margelo.nitro.nitrotext + +import androidx.annotation.Keep +import com.facebook.proguard.annotations.DoNotStrip + +/** + * Represents the JavaScript enum/union "FontWeight". + */ +@DoNotStrip +@Keep +enum class FontWeight(@DoNotStrip @Keep val value: Int) { + NORMAL(0), + BOLD(1), + ULTRALIGHT(2), + THIN(3), + LIGHT(4), + MEDIUM(5), + REGULAR(6), + SEMIBOLD(7), + CONDENSEDBOLD(8), + CONDENSED(9), + HEAVY(10), + BLACK(11); +} diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/Fragment.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/Fragment.kt new file mode 100644 index 0000000..34e46a1 --- /dev/null +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/Fragment.kt @@ -0,0 +1,71 @@ +/// +/// Fragment.kt +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +package com.margelo.nitro.nitrotext + +import androidx.annotation.Keep +import com.facebook.proguard.annotations.DoNotStrip +import com.margelo.nitro.core.* + + +/** + * Represents the JavaScript object/struct "Fragment". + */ +@DoNotStrip +@Keep +data class Fragment + @DoNotStrip + @Keep + constructor( + @DoNotStrip + @Keep + val text: String?, + @DoNotStrip + @Keep + val selectionColor: String?, + @DoNotStrip + @Keep + val fontSize: Double?, + @DoNotStrip + @Keep + val fontWeight: FontWeight?, + @DoNotStrip + @Keep + val fontColor: String?, + @DoNotStrip + @Keep + val fragmentBackgroundColor: String?, + @DoNotStrip + @Keep + val fontStyle: FontStyle?, + @DoNotStrip + @Keep + val fontFamily: String?, + @DoNotStrip + @Keep + val lineHeight: Double?, + @DoNotStrip + @Keep + val letterSpacing: Double?, + @DoNotStrip + @Keep + val textAlign: TextAlign?, + @DoNotStrip + @Keep + val textTransform: TextTransform?, + @DoNotStrip + @Keep + val textDecorationLine: TextDecorationLine?, + @DoNotStrip + @Keep + val textDecorationColor: String?, + @DoNotStrip + @Keep + val textDecorationStyle: TextDecorationStyle? + ) { + /* main constructor */ +} diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/Func_void.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/Func_void.kt new file mode 100644 index 0000000..63db75b --- /dev/null +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/Func_void.kt @@ -0,0 +1,81 @@ +/// +/// Func_void.kt +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +package com.margelo.nitro.nitrotext + +import androidx.annotation.Keep +import com.facebook.jni.HybridData +import com.facebook.proguard.annotations.DoNotStrip +import com.margelo.nitro.core.* +import dalvik.annotation.optimization.FastNative + + +/** + * Represents the JavaScript callback `() => void`. + * This can be either implemented in C++ (in which case it might be a callback coming from JS), + * or in Kotlin/Java (in which case it is a native callback). + */ +@DoNotStrip +@Keep +@Suppress("ClassName", "RedundantUnitReturnType") +fun interface Func_void: () -> Unit { + /** + * Call the given JS callback. + * @throws Throwable if the JS function itself throws an error, or if the JS function/runtime has already been deleted. + */ + @DoNotStrip + @Keep + override fun invoke(): Unit +} + +/** + * Represents the JavaScript callback `() => void`. + * This is implemented in C++, via a `std::function<...>`. + * The callback might be coming from JS. + */ +@DoNotStrip +@Keep +@Suppress( + "KotlinJniMissingFunction", "unused", + "RedundantSuppression", "RedundantUnitReturnType", "FunctionName", + "ConvertSecondaryConstructorToPrimary", "ClassName", "LocalVariableName", +) +class Func_void_cxx: Func_void { + @DoNotStrip + @Keep + private val mHybridData: HybridData + + @DoNotStrip + @Keep + private constructor(hybridData: HybridData) { + mHybridData = hybridData + } + + @DoNotStrip + @Keep + override fun invoke(): Unit + = invoke_cxx() + + @FastNative + private external fun invoke_cxx(): Unit +} + +/** + * Represents the JavaScript callback `() => void`. + * This is implemented in Java/Kotlin, via a `() -> Unit`. + * The callback is always coming from native. + */ +@DoNotStrip +@Keep +@Suppress("ClassName", "RedundantUnitReturnType", "unused") +class Func_void_java(private val function: () -> Unit): Func_void { + @DoNotStrip + @Keep + override fun invoke(): Unit { + return this.function() + } +} diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/Func_void_TextLayoutEvent.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/Func_void_TextLayoutEvent.kt new file mode 100644 index 0000000..8faef9b --- /dev/null +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/Func_void_TextLayoutEvent.kt @@ -0,0 +1,81 @@ +/// +/// Func_void_TextLayoutEvent.kt +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +package com.margelo.nitro.nitrotext + +import androidx.annotation.Keep +import com.facebook.jni.HybridData +import com.facebook.proguard.annotations.DoNotStrip +import com.margelo.nitro.core.* +import dalvik.annotation.optimization.FastNative + + +/** + * Represents the JavaScript callback `(layout: struct) => void`. + * This can be either implemented in C++ (in which case it might be a callback coming from JS), + * or in Kotlin/Java (in which case it is a native callback). + */ +@DoNotStrip +@Keep +@Suppress("ClassName", "RedundantUnitReturnType") +fun interface Func_void_TextLayoutEvent: (TextLayoutEvent) -> Unit { + /** + * Call the given JS callback. + * @throws Throwable if the JS function itself throws an error, or if the JS function/runtime has already been deleted. + */ + @DoNotStrip + @Keep + override fun invoke(layout: TextLayoutEvent): Unit +} + +/** + * Represents the JavaScript callback `(layout: struct) => void`. + * This is implemented in C++, via a `std::function<...>`. + * The callback might be coming from JS. + */ +@DoNotStrip +@Keep +@Suppress( + "KotlinJniMissingFunction", "unused", + "RedundantSuppression", "RedundantUnitReturnType", "FunctionName", + "ConvertSecondaryConstructorToPrimary", "ClassName", "LocalVariableName", +) +class Func_void_TextLayoutEvent_cxx: Func_void_TextLayoutEvent { + @DoNotStrip + @Keep + private val mHybridData: HybridData + + @DoNotStrip + @Keep + private constructor(hybridData: HybridData) { + mHybridData = hybridData + } + + @DoNotStrip + @Keep + override fun invoke(layout: TextLayoutEvent): Unit + = invoke_cxx(layout) + + @FastNative + private external fun invoke_cxx(layout: TextLayoutEvent): Unit +} + +/** + * Represents the JavaScript callback `(layout: struct) => void`. + * This is implemented in Java/Kotlin, via a `(TextLayoutEvent) -> Unit`. + * The callback is always coming from native. + */ +@DoNotStrip +@Keep +@Suppress("ClassName", "RedundantUnitReturnType", "unused") +class Func_void_TextLayoutEvent_java(private val function: (TextLayoutEvent) -> Unit): Func_void_TextLayoutEvent { + @DoNotStrip + @Keep + override fun invoke(layout: TextLayoutEvent): Unit { + return this.function(layout) + } +} diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/HybridNitroTextSpec.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/HybridNitroTextSpec.kt new file mode 100644 index 0000000..88ddc86 --- /dev/null +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/HybridNitroTextSpec.kt @@ -0,0 +1,255 @@ +/// +/// HybridNitroTextSpec.kt +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +package com.margelo.nitro.nitrotext + +import androidx.annotation.Keep +import com.facebook.jni.HybridData +import com.facebook.proguard.annotations.DoNotStrip +import com.margelo.nitro.core.* +import com.margelo.nitro.views.* + +/** + * A Kotlin class representing the NitroText HybridObject. + * Implement this abstract class to create Kotlin-based instances of NitroText. + */ +@DoNotStrip +@Keep +@Suppress( + "KotlinJniMissingFunction", "unused", + "RedundantSuppression", "RedundantUnitReturnType", "SimpleRedundantLet", + "LocalVariableName", "PropertyName", "PrivatePropertyName", "FunctionName" +) +abstract class HybridNitroTextSpec: HybridView() { + @DoNotStrip + private var mHybridData: HybridData = initHybrid() + + init { + super.updateNative(mHybridData) + } + + override fun updateNative(hybridData: HybridData) { + mHybridData = hybridData + super.updateNative(hybridData) + } + + // Properties + @get:DoNotStrip + @get:Keep + @set:DoNotStrip + @set:Keep + abstract var fragments: Array? + + @get:DoNotStrip + @get:Keep + @set:DoNotStrip + @set:Keep + abstract var selectable: Boolean? + + @get:DoNotStrip + @get:Keep + @set:DoNotStrip + @set:Keep + abstract var allowFontScaling: Boolean? + + @get:DoNotStrip + @get:Keep + @set:DoNotStrip + @set:Keep + abstract var ellipsizeMode: EllipsizeMode? + + @get:DoNotStrip + @get:Keep + @set:DoNotStrip + @set:Keep + abstract var numberOfLines: Double? + + @get:DoNotStrip + @get:Keep + @set:DoNotStrip + @set:Keep + abstract var lineBreakStrategyIOS: LineBreakStrategyIOS? + + @get:DoNotStrip + @get:Keep + @set:DoNotStrip + @set:Keep + abstract var dynamicTypeRamp: DynamicTypeRamp? + + @get:DoNotStrip + @get:Keep + @set:DoNotStrip + @set:Keep + abstract var maxFontSizeMultiplier: Double? + + @get:DoNotStrip + @get:Keep + @set:DoNotStrip + @set:Keep + abstract var adjustsFontSizeToFit: Boolean? + + @get:DoNotStrip + @get:Keep + @set:DoNotStrip + @set:Keep + abstract var minimumFontScale: Double? + + abstract var onTextLayout: ((layout: TextLayoutEvent) -> Unit)? + + private var onTextLayout_cxx: Func_void_TextLayoutEvent? + @Keep + @DoNotStrip + get() { + return onTextLayout?.let { Func_void_TextLayoutEvent_java(it) } + } + @Keep + @DoNotStrip + set(value) { + onTextLayout = value?.let { it } + } + + abstract var onPress: (() -> Unit)? + + private var onPress_cxx: Func_void? + @Keep + @DoNotStrip + get() { + return onPress?.let { Func_void_java(it) } + } + @Keep + @DoNotStrip + set(value) { + onPress = value?.let { it } + } + + abstract var onPressIn: (() -> Unit)? + + private var onPressIn_cxx: Func_void? + @Keep + @DoNotStrip + get() { + return onPressIn?.let { Func_void_java(it) } + } + @Keep + @DoNotStrip + set(value) { + onPressIn = value?.let { it } + } + + abstract var onPressOut: (() -> Unit)? + + private var onPressOut_cxx: Func_void? + @Keep + @DoNotStrip + get() { + return onPressOut?.let { Func_void_java(it) } + } + @Keep + @DoNotStrip + set(value) { + onPressOut = value?.let { it } + } + + @get:DoNotStrip + @get:Keep + @set:DoNotStrip + @set:Keep + abstract var text: String? + + @get:DoNotStrip + @get:Keep + @set:DoNotStrip + @set:Keep + abstract var selectionColor: String? + + @get:DoNotStrip + @get:Keep + @set:DoNotStrip + @set:Keep + abstract var fontSize: Double? + + @get:DoNotStrip + @get:Keep + @set:DoNotStrip + @set:Keep + abstract var fontWeight: FontWeight? + + @get:DoNotStrip + @get:Keep + @set:DoNotStrip + @set:Keep + abstract var fontColor: String? + + @get:DoNotStrip + @get:Keep + @set:DoNotStrip + @set:Keep + abstract var fragmentBackgroundColor: String? + + @get:DoNotStrip + @get:Keep + @set:DoNotStrip + @set:Keep + abstract var fontStyle: FontStyle? + + @get:DoNotStrip + @get:Keep + @set:DoNotStrip + @set:Keep + abstract var fontFamily: String? + + @get:DoNotStrip + @get:Keep + @set:DoNotStrip + @set:Keep + abstract var lineHeight: Double? + + @get:DoNotStrip + @get:Keep + @set:DoNotStrip + @set:Keep + abstract var letterSpacing: Double? + + @get:DoNotStrip + @get:Keep + @set:DoNotStrip + @set:Keep + abstract var textAlign: TextAlign? + + @get:DoNotStrip + @get:Keep + @set:DoNotStrip + @set:Keep + abstract var textTransform: TextTransform? + + @get:DoNotStrip + @get:Keep + @set:DoNotStrip + @set:Keep + abstract var textDecorationLine: TextDecorationLine? + + @get:DoNotStrip + @get:Keep + @set:DoNotStrip + @set:Keep + abstract var textDecorationColor: String? + + @get:DoNotStrip + @get:Keep + @set:DoNotStrip + @set:Keep + abstract var textDecorationStyle: TextDecorationStyle? + + // Methods + + + private external fun initHybrid(): HybridData + + companion object { + private const val TAG = "HybridNitroTextSpec" + } +} diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/LineBreakStrategyIOS.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/LineBreakStrategyIOS.kt new file mode 100644 index 0000000..16cef11 --- /dev/null +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/LineBreakStrategyIOS.kt @@ -0,0 +1,23 @@ +/// +/// LineBreakStrategyIOS.kt +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +package com.margelo.nitro.nitrotext + +import androidx.annotation.Keep +import com.facebook.proguard.annotations.DoNotStrip + +/** + * Represents the JavaScript enum/union "LineBreakStrategyIOS". + */ +@DoNotStrip +@Keep +enum class LineBreakStrategyIOS(@DoNotStrip @Keep val value: Int) { + NONE(0), + STANDARD(1), + HANGUL_WORD(2), + PUSH_OUT(3); +} diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/NitroTextOnLoad.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/NitroTextOnLoad.kt new file mode 100644 index 0000000..c88d798 --- /dev/null +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/NitroTextOnLoad.kt @@ -0,0 +1,35 @@ +/// +/// NitroTextOnLoad.kt +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +package com.margelo.nitro.nitrotext + +import android.util.Log + +internal class NitroTextOnLoad { + companion object { + private const val TAG = "NitroTextOnLoad" + private var didLoad = false + /** + * Initializes the native part of "NitroText". + * This method is idempotent and can be called more than once. + */ + @JvmStatic + fun initializeNative() { + if (didLoad) return + try { + Log.i(TAG, "Loading NitroText C++ library...") + System.loadLibrary("NitroText") + Log.i(TAG, "Successfully loaded NitroText C++ library!") + didLoad = true + } catch (e: Error) { + Log.e(TAG, "Failed to load NitroText C++ library! Is it properly installed and linked? " + + "Is the name correct? (see `CMakeLists.txt`, at `add_library(...)`)", e) + throw e + } + } + } +} diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/TextAlign.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/TextAlign.kt new file mode 100644 index 0000000..92daeba --- /dev/null +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/TextAlign.kt @@ -0,0 +1,24 @@ +/// +/// TextAlign.kt +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +package com.margelo.nitro.nitrotext + +import androidx.annotation.Keep +import com.facebook.proguard.annotations.DoNotStrip + +/** + * Represents the JavaScript enum/union "TextAlign". + */ +@DoNotStrip +@Keep +enum class TextAlign(@DoNotStrip @Keep val value: Int) { + AUTO(0), + LEFT(1), + RIGHT(2), + CENTER(3), + JUSTIFY(4); +} diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/TextDecorationLine.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/TextDecorationLine.kt new file mode 100644 index 0000000..654ac33 --- /dev/null +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/TextDecorationLine.kt @@ -0,0 +1,23 @@ +/// +/// TextDecorationLine.kt +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +package com.margelo.nitro.nitrotext + +import androidx.annotation.Keep +import com.facebook.proguard.annotations.DoNotStrip + +/** + * Represents the JavaScript enum/union "TextDecorationLine". + */ +@DoNotStrip +@Keep +enum class TextDecorationLine(@DoNotStrip @Keep val value: Int) { + NONE(0), + UNDERLINE(1), + LINE_THROUGH(2), + UNDERLINE_LINE_THROUGH(3); +} diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/TextDecorationStyle.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/TextDecorationStyle.kt new file mode 100644 index 0000000..28bba8e --- /dev/null +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/TextDecorationStyle.kt @@ -0,0 +1,23 @@ +/// +/// TextDecorationStyle.kt +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +package com.margelo.nitro.nitrotext + +import androidx.annotation.Keep +import com.facebook.proguard.annotations.DoNotStrip + +/** + * Represents the JavaScript enum/union "TextDecorationStyle". + */ +@DoNotStrip +@Keep +enum class TextDecorationStyle(@DoNotStrip @Keep val value: Int) { + SOLID(0), + DOUBLE(1), + DOTTED(2), + DASHED(3); +} diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/TextLayout.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/TextLayout.kt new file mode 100644 index 0000000..7866d8f --- /dev/null +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/TextLayout.kt @@ -0,0 +1,53 @@ +/// +/// TextLayout.kt +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +package com.margelo.nitro.nitrotext + +import androidx.annotation.Keep +import com.facebook.proguard.annotations.DoNotStrip +import com.margelo.nitro.core.* + + +/** + * Represents the JavaScript object/struct "TextLayout". + */ +@DoNotStrip +@Keep +data class TextLayout + @DoNotStrip + @Keep + constructor( + @DoNotStrip + @Keep + val text: String, + @DoNotStrip + @Keep + val x: Double, + @DoNotStrip + @Keep + val y: Double, + @DoNotStrip + @Keep + val width: Double, + @DoNotStrip + @Keep + val height: Double, + @DoNotStrip + @Keep + val descender: Double, + @DoNotStrip + @Keep + val capHeight: Double, + @DoNotStrip + @Keep + val ascender: Double, + @DoNotStrip + @Keep + val xHeight: Double + ) { + /* main constructor */ +} diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/TextLayoutEvent.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/TextLayoutEvent.kt new file mode 100644 index 0000000..90b29ff --- /dev/null +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/TextLayoutEvent.kt @@ -0,0 +1,29 @@ +/// +/// TextLayoutEvent.kt +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +package com.margelo.nitro.nitrotext + +import androidx.annotation.Keep +import com.facebook.proguard.annotations.DoNotStrip +import com.margelo.nitro.core.* + + +/** + * Represents the JavaScript object/struct "TextLayoutEvent". + */ +@DoNotStrip +@Keep +data class TextLayoutEvent + @DoNotStrip + @Keep + constructor( + @DoNotStrip + @Keep + val lines: Array + ) { + /* main constructor */ +} diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/TextTransform.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/TextTransform.kt new file mode 100644 index 0000000..3c76277 --- /dev/null +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/TextTransform.kt @@ -0,0 +1,23 @@ +/// +/// TextTransform.kt +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +package com.margelo.nitro.nitrotext + +import androidx.annotation.Keep +import com.facebook.proguard.annotations.DoNotStrip + +/** + * Represents the JavaScript enum/union "TextTransform". + */ +@DoNotStrip +@Keep +enum class TextTransform(@DoNotStrip @Keep val value: Int) { + NONE(0), + UPPERCASE(1), + LOWERCASE(2), + CAPITALIZE(3); +} diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/views/HybridNitroTextManager.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/views/HybridNitroTextManager.kt new file mode 100644 index 0000000..7a5695c --- /dev/null +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/views/HybridNitroTextManager.kt @@ -0,0 +1,50 @@ +/// +/// HybridNitroTextManager.kt +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +package com.margelo.nitro.nitrotext.views + +import android.view.View +import com.facebook.react.uimanager.ReactStylesDiffMap +import com.facebook.react.uimanager.SimpleViewManager +import com.facebook.react.uimanager.StateWrapper +import com.facebook.react.uimanager.ThemedReactContext +import com.nitrotext.* + +/** + * Represents the React Native `ViewManager` for the "NitroText" Nitro HybridView. + */ +open class HybridNitroTextManager: SimpleViewManager() { + private val views = hashMapOf() + + override fun getName(): String { + return "NitroText" + } + + override fun createViewInstance(reactContext: ThemedReactContext): View { + val hybridView = HybridNitroText(reactContext) + val view = hybridView.view + views[view] = hybridView + return view + } + + override fun onDropViewInstance(view: View) { + super.onDropViewInstance(view) + views.remove(view) + } + + override fun updateState(view: View, props: ReactStylesDiffMap, stateWrapper: StateWrapper): Any? { + val hybridView = views[view] ?: throw Error("Couldn't find view $view in local views table!") + + // 1. Update each prop individually + hybridView.beforeUpdate() + HybridNitroTextStateUpdater.updateViewProps(hybridView, stateWrapper) + hybridView.afterUpdate() + + // 2. Continue in base View props + return super.updateState(view, props, stateWrapper) + } +} diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/views/HybridNitroTextStateUpdater.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/views/HybridNitroTextStateUpdater.kt new file mode 100644 index 0000000..399877d --- /dev/null +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/views/HybridNitroTextStateUpdater.kt @@ -0,0 +1,23 @@ +/// +/// HybridNitroTextStateUpdater.kt +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +package com.margelo.nitro.nitrotext.views + +import com.facebook.react.uimanager.StateWrapper +import com.margelo.nitro.nitrotext.* + +internal class HybridNitroTextStateUpdater { + companion object { + /** + * Updates the props for [view] through C++. + * The [state] prop is expected to contain [view]'s props as wrapped Fabric state. + */ + @Suppress("KotlinJniMissingFunction") + @JvmStatic + external fun updateViewProps(view: HybridNitroTextSpec, state: StateWrapper) + } +} diff --git a/nitrogen/generated/shared/c++/views/HybridNitroTextComponent.hpp b/nitrogen/generated/shared/c++/views/HybridNitroTextComponent.hpp index b811738..d7431c9 100644 --- a/nitrogen/generated/shared/c++/views/HybridNitroTextComponent.hpp +++ b/nitrogen/generated/shared/c++/views/HybridNitroTextComponent.hpp @@ -19,58 +19,20 @@ #include "Fragment.hpp" #include #include -#include -#include #include "EllipsizeMode.hpp" -#include -#include #include "LineBreakStrategyIOS.hpp" -#include #include "DynamicTypeRamp.hpp" -#include -#include -#include -#include #include "TextLayoutEvent.hpp" #include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include -#include -#include #include "FontWeight.hpp" -#include -#include -#include -#include -#include #include "FontStyle.hpp" -#include -#include -#include -#include -#include #include "TextAlign.hpp" -#include #include "TextTransform.hpp" -#include #include "TextDecorationLine.hpp" -#include -#include -#include #include "TextDecorationStyle.hpp" -#include #include #include "HybridNitroTextSpec.hpp" -#include -#include namespace margelo::nitro::nitrotext::views { diff --git a/package.json b/package.json index cba0893..1af0515 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "clean": "git clean -dfX", "release": "semantic-release", "build": "bun run typecheck && bob build", - "codegen": "nitro-codegen --logLevel=\"debug\" && bun run build", + "codegen": "nitrogen --logLevel=\"debug\" && bun run build && node post-script.js", "postcodegen": "bun --cwd example pod" }, "keywords": [ @@ -59,12 +59,12 @@ "@semantic-release/git": "^10.0.1", "@types/jest": "^29.5.12", "@types/react": "19.1.0", + "conventional-changelog-conventionalcommits": "^9.1.0", "nitrogen": "^0.29.6", "react": "19.1.0", "react-native": "0.81.4", "react-native-builder-bob": "^0.37.0", "react-native-nitro-modules": "^0.29.6", - "conventional-changelog-conventionalcommits": "^9.1.0", "semantic-release": "^24.2.8", "typescript": "^5.8.3" }, diff --git a/post-script.js b/post-script.js new file mode 100644 index 0000000..0202df9 --- /dev/null +++ b/post-script.js @@ -0,0 +1,32 @@ +/** +* @file This script is auto-generated by create-nitro-module and should not be edited. +* +* @description This script applies a workaround for Android by modifying the 'OnLoad.cpp' file. +* It reads the file content and removes the 'margelo/nitro/' string from it. This enables support for custom package names. +* +* @module create-nitro-module +*/ +const path = require('node:path') +const { writeFile, readFile } = require('node:fs/promises') + +const androidWorkaround = async () => { + const androidOnLoadFile = path.join( + process.cwd(), + 'nitrogen/generated/android', + 'NitroTextOnLoad.cpp' + ) + + const viewManagerFile = path.join( + process.cwd(), + 'nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/views', + 'HybridNitroTextManager.kt' + ) + + const viewManagerStr = await readFile(viewManagerFile, { encoding: 'utf8' }) + await writeFile(viewManagerFile, viewManagerStr.replace(/com\.margelo\.nitro\.nitrotext\.\*/g, 'com.nitrotext.*')) + + + const str = await readFile(androidOnLoadFile, { encoding: 'utf8' }) + await writeFile(androidOnLoadFile, str.replace(/margelo\/nitro\//g, '')) +} +androidWorkaround() \ No newline at end of file diff --git a/src/nitro-text.tsx b/src/nitro-text.tsx index 261982a..e462713 100644 --- a/src/nitro-text.tsx +++ b/src/nitro-text.tsx @@ -42,7 +42,7 @@ export const NitroText = (props: NitroTextPropsWithEvents) => { const { children, style, - selectable = true, + selectable, selectionColor, onTextLayout, onPress, @@ -60,7 +60,7 @@ export const NitroText = (props: NitroTextPropsWithEvents) => { return flattenChildrenToFragments(children, style as any) }, [children, style, isSimpleText]) - if (isInsideRNText || Platform.OS === 'android') { + if (isInsideRNText) { const onRNTextLayout = useCallback( (e: TextLayoutEvent) => { onTextLayout?.(e.nativeEvent) @@ -124,8 +124,10 @@ export const NitroText = (props: NitroTextPropsWithEvents) => { selectionColor={selectionColor as string} style={style} fontColor={topStyles.fontColor} + fontSize={topStyles.fontSize} fontWeight={topStyles.fontWeight} fontStyle={topStyles.fontStyle} + lineHeight={topStyles.lineHeight} letterSpacing={topStyles.letterSpacing} textAlign={topStyles.textAlign} textTransform={topStyles.textTransform} diff --git a/src/specs/nitro-text.nitro.ts b/src/specs/nitro-text.nitro.ts index d3129cd..84178a3 100755 --- a/src/specs/nitro-text.nitro.ts +++ b/src/specs/nitro-text.nitro.ts @@ -99,5 +99,5 @@ export interface NitroTextMethods extends HybridViewMethods { } export type NitroText = HybridView< NitroTextProps, NitroTextMethods, - { ios: 'swift' } + { ios: 'swift', android: 'kotlin' } > From 48ec631836ad1d069451c699ad9c9d003b0986d0 Mon Sep 17 00:00:00 2001 From: Patrick Kabwe Date: Sun, 23 Nov 2025 21:30:01 +0200 Subject: [PATCH 06/12] feat: add Android support and custom menu implementation - Add Android native implementation for NitroText - Implement custom menu support with MenuItem type - Add menus prop to NitroText component - Update Android build configuration - Add MenuItem bridge code for Android - Update component descriptors and specs for menu support --- android/build.gradle | 2 - .../java/com/nitrotext/HybridNitroText.kt | 16 +- .../main/java/com/nitrotext/NitroTextImpl.kt | 93 ++++----- .../main/java/com/nitrotext/NitroTextView.kt | 185 +++++++++++++++++- cpp/NitroTextComponentDescriptor.cpp | 2 +- ios/HybridNitroText.swift | 13 -- ios/NitroTextImpl.swift | 2 +- .../generated/android/NitroTextOnLoad.cpp | 6 +- nitrogen/generated/android/c++/JFragment.hpp | 14 +- nitrogen/generated/android/c++/JFunc_void.hpp | 3 +- .../c++/JFunc_void_TextLayoutEvent.hpp | 3 +- .../android/c++/JHybridNitroTextSpec.cpp | 113 ++++++----- .../android/c++/JHybridNitroTextSpec.hpp | 9 +- nitrogen/generated/android/c++/JMenuItem.hpp | 72 +++++++ .../generated/android/c++/JTextLayout.hpp | 6 +- .../android/c++/JTextLayoutEvent.hpp | 9 +- .../views/JHybridNitroTextStateUpdater.cpp | 8 +- .../com/margelo/nitro/nitrotext/Fragment.kt | 108 +++++----- .../com/margelo/nitro/nitrotext/Func_void.kt | 1 - .../nitrotext/Func_void_TextLayoutEvent.kt | 1 - .../nitro/nitrotext/HybridNitroTextSpec.kt | 24 ++- .../com/margelo/nitro/nitrotext/MenuItem.kt | 45 +++++ .../com/margelo/nitro/nitrotext/TextLayout.kt | 73 ++++--- .../nitro/nitrotext/TextLayoutEvent.kt | 21 +- .../nitrotext/views/HybridNitroTextManager.kt | 4 +- src/nitro-text.tsx | 2 +- 26 files changed, 572 insertions(+), 263 deletions(-) create mode 100644 nitrogen/generated/android/c++/JMenuItem.hpp create mode 100644 nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/MenuItem.kt diff --git a/android/build.gradle b/android/build.gradle index e6affaa..cb25ab0 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -137,8 +137,6 @@ dependencies { // Add a dependency on NitroModules implementation project(":react-native-nitro-modules") - implementation "org.jsoup:jsoup:1.18.1" - } if (isNewArchitectureEnabled()) { diff --git a/android/src/main/java/com/nitrotext/HybridNitroText.kt b/android/src/main/java/com/nitrotext/HybridNitroText.kt index 62189f3..9e68108 100755 --- a/android/src/main/java/com/nitrotext/HybridNitroText.kt +++ b/android/src/main/java/com/nitrotext/HybridNitroText.kt @@ -22,16 +22,10 @@ class HybridNitroText(val context: ThemedReactContext) : HybridNitroTextSpec(), impl.setFragments(value) } - override var renderer: NitroRenderer? + override var renderer: Renderer? get() = null set(value) { - impl.setRenderer(value) - } - - override var richTextStyleRules: Array? - get() = null - set(value) { - impl.setRichTextStyleRules(value) + // HTML parsing is now done in JS/TS layer, renderer is ignored } override var selectable: Boolean? @@ -86,6 +80,12 @@ class HybridNitroText(val context: ThemedReactContext) : HybridNitroTextSpec(), get() = null set(value) { value } + override var menus: Array? + get() = null + set(value) { + view.customMenus = value + } + override var onTextLayout: ((TextLayoutEvent) -> Unit)? get() = onTextLayoutCallback set(value) { diff --git a/android/src/main/java/com/nitrotext/NitroTextImpl.kt b/android/src/main/java/com/nitrotext/NitroTextImpl.kt index ef85682..a52f94c 100644 --- a/android/src/main/java/com/nitrotext/NitroTextImpl.kt +++ b/android/src/main/java/com/nitrotext/NitroTextImpl.kt @@ -7,28 +7,26 @@ import android.text.SpannableStringBuilder import android.text.TextUtils import android.text.style.AbsoluteSizeSpan import android.text.style.BackgroundColorSpan -import android.text.style.ClickableSpan import android.text.style.ForegroundColorSpan import android.text.style.StrikethroughSpan import android.text.style.StyleSpan import android.text.style.TypefaceSpan import android.text.style.UnderlineSpan import android.view.Gravity -import android.text.method.ArrowKeyMovementMethod -import android.text.method.LinkMovementMethod import androidx.appcompat.widget.AppCompatTextView import com.facebook.react.uimanager.PixelUtil import com.margelo.nitro.nitrotext.* import androidx.core.graphics.toColorInt -import com.nitrotext.renderers.NitroHtmlRenderer import com.nitrotext.spans.NitroLineHeightSpan +import com.nitrotext.spans.UrlSpanNoUnderline +import android.text.method.LinkMovementMethod +import android.text.method.ArrowKeyMovementMethod +import com.nitrotext.HtmlLinkMovementMethod class NitroTextImpl(private val view: AppCompatTextView) { // Stored props private var fragments: Array? = null private var text: String? = null - private var renderer: NitroRenderer = NitroRenderer.PLAINTEXT - private var richTextStyleRules: Array? = null private var selectable: Boolean? = null private var selectionColor: String? = null @@ -63,25 +61,23 @@ class NitroTextImpl(private val view: AppCompatTextView) { applyAlignment() val frags = fragments - val content = text - - if (renderer == NitroRenderer.HTML && !content.isNullOrEmpty()) { - applyHtml(content) - } else if (!frags.isNullOrEmpty()) { + + if (!frags.isNullOrEmpty()) { applyFragments(frags) } else { applySimpleText() } applyLetterSpacing() + + // Request layout update so container can resize based on content + // This will trigger onMeasure which will calculate the correct height + view.requestLayout() } // Setters fun setFragments(value: Array?) { fragments = value } fun setText(value: String?) { text = value } - fun setRenderer(value: NitroRenderer?) { renderer = value ?: NitroRenderer.PLAINTEXT } - fun setRichTextStyleRules(value: Array?) { richTextStyleRules = value } - fun setSelectable(value: Boolean?) { selectable = value } fun setSelectionColor(value: String?) { selectionColor = value } fun setNumberOfLines(value: Double?) { numberOfLines = value } @@ -190,6 +186,7 @@ class NitroTextImpl(private val view: AppCompatTextView) { private fun applyFragments(fragments: Array) { val builder = SpannableStringBuilder() val containerLineHeightPx = resolveLineHeight(lineHeight) + var hasLinks = false var start: Int for (frag in fragments) { val fragText = transformText(frag.text, frag.textTransform) ?: "" @@ -222,12 +219,36 @@ class NitroTextImpl(private val view: AppCompatTextView) { frag.lineHeight?.let { lh -> resolveLineHeight(lh)?.let { applyLineHeightSpan(builder, start, end, it) } } + // Links + frag.linkUrl?.let { url -> + if (url.isNotEmpty()) { + builder.setSpan(UrlSpanNoUnderline(url), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + hasLinks = true + // Apply default link color if fragment doesn't have explicit color + if (frag.fontColor == null) { + // Use system link color (typically blue) + val linkColor = android.graphics.Color.parseColor("#007AFF") // iOS-like blue + builder.setSpan(ForegroundColorSpan(linkColor), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + } + } + } } containerLineHeightPx?.let { applyLineHeightSpan(builder, 0, builder.length, it) } view.text = builder // Apply default text color for runs without explicit color view.setTextColor(resolvedFontColor()) + + // Set up movement method for links + val isSelectable = selectable == true + view.linksClickable = hasLinks + view.movementMethod = when { + hasLinks && isSelectable -> LinkMovementMethod.getInstance() + hasLinks -> HtmlLinkMovementMethod + isSelectable -> ArrowKeyMovementMethod.getInstance() + else -> null + } + view.setTextIsSelectable(isSelectable) } private fun applyDecorationSpans(builder: SpannableStringBuilder, start: Int, end: Int, line: TextDecorationLine?) { @@ -278,50 +299,6 @@ class NitroTextImpl(private val view: AppCompatTextView) { return parsed ?: Color.BLACK } - private fun baseRichTextStyle(): RichTextStyle { - return RichTextStyle( - fontColor = fontColor, - fragmentBackgroundColor = fragmentBackgroundColor, - fontSize = fontSize, - fontWeight = fontWeight, - fontStyle = fontStyle, - fontFamily = fontFamily, - lineHeight = lineHeight, - letterSpacing = letterSpacing, - textAlign = textAlign, - textTransform = textTransform, - textDecorationLine = textDecorationLine, - textDecorationColor = textDecorationColor, - textDecorationStyle = textDecorationStyle, - marginTop = null, - marginBottom = null, - marginLeft = null, - marginRight = null, - ) - } - - private fun applyHtml(html: String) { - val renderer = NitroHtmlRenderer( - context = view.context, - defaultTextSizePx = view.textSize, - allowFontScaling = allowFontScaling, - maxFontSizeMultiplier = maxFontSizeMultiplier, - ) - val spannable = renderer.render(html, baseRichTextStyle(), richTextStyleRules) - trimTrailingNewlines(spannable) - view.text = spannable - val hasLinks = spannable.getSpans(0, spannable.length, ClickableSpan::class.java).isNotEmpty() - view.linksClickable = hasLinks - val isSelectable = selectable == true - view.movementMethod = when { - hasLinks && isSelectable -> LinkMovementMethod.getInstance() - hasLinks -> HtmlLinkMovementMethod - isSelectable -> ArrowKeyMovementMethod.getInstance() - else -> null - } - view.setTextIsSelectable(isSelectable) - view.setTextColor(resolvedFontColor()) - } private fun trimTrailingNewlines(builder: SpannableStringBuilder) { var length = builder.length diff --git a/android/src/main/java/com/nitrotext/NitroTextView.kt b/android/src/main/java/com/nitrotext/NitroTextView.kt index 6ae7e14..5300f09 100644 --- a/android/src/main/java/com/nitrotext/NitroTextView.kt +++ b/android/src/main/java/com/nitrotext/NitroTextView.kt @@ -5,12 +5,18 @@ import android.content.Context import android.graphics.Rect import android.graphics.text.LineBreaker import android.text.Layout +import android.text.StaticLayout import android.text.TextPaint +import android.text.TextUtils +import android.view.ActionMode +import android.view.Menu +import android.view.MenuItem as AndroidMenuItem import android.view.View import androidx.appcompat.widget.AppCompatTextView import com.facebook.react.views.view.ReactViewGroup import com.margelo.nitro.nitrotext.TextLayout import com.margelo.nitro.nitrotext.TextLayoutEvent +import com.margelo.nitro.nitrotext.MenuItem interface NitroTextViewDelegate { @@ -25,6 +31,11 @@ class NitroTextView(ctx: Context) : ReactViewGroup(ctx) { breakStrategy = LineBreaker.BREAK_STRATEGY_HIGH_QUALITY hyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NORMAL setHorizontallyScrolling(false) + // Disable scrolling - container should size based on content + isVerticalScrollBarEnabled = false + isHorizontalScrollBarEnabled = false + movementMethod = null // Prevent scrolling via movement method + // Ensure TextView can expand to show all content } var nitroTextDelegate: NitroTextViewDelegate? = null @@ -32,26 +43,196 @@ class NitroTextView(ctx: Context) : ReactViewGroup(ctx) { field = value scheduleTextLayoutDispatch() } + + var customMenus: Array? = null + set(value) { + field = value + updateActionModeCallback() + } private var pendingLayoutDispatch = false + + private fun updateActionModeCallback() { + textView.customSelectionActionModeCallback = customMenus?.takeIf { it.isNotEmpty() } + ?.let { createCustomActionModeCallback(it) } + } + + private fun createCustomActionModeCallback(menus: Array): ActionMode.Callback { + val menuItemMap = menus.mapIndexedNotNull { index, item -> + if (item.title.isNotEmpty()) { Menu.FIRST + index to item } else null + }.toMap() + + return object : ActionMode.Callback { + override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean { + menuItemMap.forEach { (id, item) -> + menu?.add(Menu.NONE, id, Menu.NONE, item.title) + } + return true + } + + override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?) = true + + override fun onActionItemClicked(mode: ActionMode?, item: AndroidMenuItem?): Boolean { + menuItemMap[item?.itemId]?.let { + it.action.invoke() + mode?.finish() + return true + } + return false + } + + override fun onDestroyActionMode(mode: ActionMode?) {} + } + } init { // Fill the container; borders/radius are applied to this container by RN. + // TextView should wrap content height, but match parent width addView( textView, LayoutParams( LayoutParams.MATCH_PARENT, - LayoutParams.MATCH_PARENT + LayoutParams.WRAP_CONTENT ) ) } + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + // Measure the TextView first to get its desired size + val width = MeasureSpec.getSize(widthMeasureSpec) + val availableWidth = (width - paddingLeft - paddingRight).coerceAtLeast(0) + + if (availableWidth <= 0) { + // No width available, return minimum size + setMeasuredDimension(width, 0) + return + } + + val widthSpec = MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.EXACTLY) + + // Measure TextView with a very large height to ensure layout is fully calculated + // This allows TextView to calculate its complete layout including all spans + // Using a large AT_MOST constraint ensures TextView measures all content + val largeHeightSpec = MeasureSpec.makeMeasureSpec(Int.MAX_VALUE shr 2, MeasureSpec.AT_MOST) + textView.measure(widthSpec, largeHeightSpec) + + // Try to get the layout from TextView - this is the most accurate + var textViewHeight = textView.measuredHeight + val layout = textView.layout + + if (layout != null && layout.lineCount > 0) { + // Use layout height which accurately accounts for all spans + // getLineBottom returns the bottom of the line including all spacing from spans + val lastLineBottom = layout.getLineBottom(layout.lineCount - 1) + val paddingTop = textView.totalPaddingTop + val paddingBottom = textView.totalPaddingBottom + textViewHeight = lastLineBottom + paddingTop + paddingBottom + } else { + // Layout not available yet - TextView's measuredHeight should account for spans + // when measured with a large height constraint + // However, if measuredHeight seems too small, try calculating with StaticLayout + val text = textView.text + if (text != null && text.isNotEmpty() && textView.measuredHeight < 100) { + // Measured height seems suspiciously small, try StaticLayout as fallback + // Note: StaticLayout won't account for custom spans, but gives a baseline + val paint = TextPaint(textView.paint) + val alignment = when (textView.textAlignment) { + View.TEXT_ALIGNMENT_CENTER -> Layout.Alignment.ALIGN_CENTER + View.TEXT_ALIGNMENT_TEXT_END, View.TEXT_ALIGNMENT_VIEW_END -> Layout.Alignment.ALIGN_OPPOSITE + else -> Layout.Alignment.ALIGN_NORMAL + } + + val staticLayout = StaticLayout.Builder + .obtain(text, 0, text.length, paint, availableWidth) + .setAlignment(alignment) + .setLineSpacing(textView.lineSpacingExtra, textView.lineSpacingMultiplier) + .setIncludePad(textView.includeFontPadding) + .setBreakStrategy(textView.breakStrategy) + .setHyphenationFrequency(textView.hyphenationFrequency) + .setMaxLines(Int.MAX_VALUE) + .build() + + if (staticLayout.lineCount > 0) { + val staticHeight = staticLayout.getLineBottom(staticLayout.lineCount - 1) + + textView.totalPaddingTop + + textView.totalPaddingBottom + // Use the larger of measured height or static layout height + // Measured height should account for spans, static layout gives baseline + textViewHeight = textViewHeight.coerceAtLeast(staticHeight) + } + } + // Otherwise trust TextView's measuredHeight which should be accurate when measured with large height + } + + // Container height should wrap content (TextView's content height + container padding) + val totalHeight = textViewHeight + paddingTop + paddingBottom + val height = MeasureSpec.getSize(heightMeasureSpec) + val finalHeight = when (MeasureSpec.getMode(heightMeasureSpec)) { + MeasureSpec.UNSPECIFIED -> totalHeight + MeasureSpec.AT_MOST -> { + // Report the actual content height we need + // If it's larger than the constraint, we still report it so container can expand + totalHeight + } + MeasureSpec.EXACTLY -> { + // For EXACTLY, we must use at least the content height + // If constraint is smaller, we still need to report our actual height + height.coerceAtLeast(totalHeight) + } + else -> totalHeight + } + + setMeasuredDimension(width, finalHeight) + } + override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { val childLeft = paddingLeft val childTop = paddingTop val childRight = measuredWidth - paddingRight - val childBottom = measuredHeight - paddingBottom + + // Calculate TextView height from layout if available + val layout = textView.layout + val textViewHeight = if (layout != null && layout.lineCount > 0) { + val lastLineBottom = layout.getLineBottom(layout.lineCount - 1) + val paddingTop = textView.totalPaddingTop + val paddingBottom = textView.totalPaddingBottom + lastLineBottom + paddingTop + paddingBottom + } else { + textView.measuredHeight + } + + val childBottom = childTop + textViewHeight textView.layout(childLeft, childTop, childRight, childBottom) + + // After layout, verify the TextView's layout height matches our container height + // If layout shows more content, request a remeasure to expand container + if (layout != null && layout.lineCount > 0) { + val actualLayoutHeight = layout.getLineBottom(layout.lineCount - 1) + + textView.totalPaddingTop + + textView.totalPaddingBottom + val containerContentHeight = measuredHeight - paddingTop - paddingBottom + + // Check if content is being cut off + // Use a larger tolerance (5px) to account for rounding and span calculations + if (actualLayoutHeight > containerContentHeight + 5) { + // Layout shows more content than container, request remeasure + // This will trigger onMeasure again with the correct height + // Use post to avoid layout during layout + post { + // Force a full remeasure by invalidating the measurement cache + forceLayout() + requestLayout() + } + } + } else if (layout == null && textView.text != null && textView.text.isNotEmpty()) { + // Layout not calculated yet but text exists - request remeasure + // This can happen if text was set but layout wasn't calculated + post { + forceLayout() + requestLayout() + } + } + scheduleTextLayoutDispatch() } diff --git a/cpp/NitroTextComponentDescriptor.cpp b/cpp/NitroTextComponentDescriptor.cpp index c47f374..e156957 100644 --- a/cpp/NitroTextComponentDescriptor.cpp +++ b/cpp/NitroTextComponentDescriptor.cpp @@ -4,7 +4,7 @@ // #include -#import "../cpp/NitroTextComponentDescriptor.hpp" +#include "NitroTextComponentDescriptor.hpp" using namespace facebook; using namespace margelo::nitro::nitrotext::views; diff --git a/ios/HybridNitroText.swift b/ios/HybridNitroText.swift index 31bd1cf..ae0f70a 100755 --- a/ios/HybridNitroText.swift +++ b/ios/HybridNitroText.swift @@ -32,19 +32,6 @@ class HybridNitroText: HybridNitroTextSpec, NitroTextViewDelegate { } } - var renderer: NitroRenderer? { - didSet { - // HTML rendering is currently implemented on Android only. - // Keep tracking the requested renderer to avoid dropping the prop. - } - } - - var richTextStyleRules: [RichTextStyleRule]? { - didSet { - // No-op on iOS for now. - } - } - var selectable: Bool? { didSet { nitroTextImpl.setSelectable(selectable) diff --git a/ios/NitroTextImpl.swift b/ios/NitroTextImpl.swift index ffa7844..6cd48e9 100644 --- a/ios/NitroTextImpl.swift +++ b/ios/NitroTextImpl.swift @@ -441,6 +441,6 @@ extension NitroTextImpl { a.textDecorationStyle == b.textDecorationStyle && a.fragmentBackgroundColor == b.fragmentBackgroundColor && a.selectionColor == b.selectionColor && - a.linkUrl == b.linkUrl // Important: links must match for fragments to be identical + a.linkUrl == b.linkUrl } } diff --git a/nitrogen/generated/android/NitroTextOnLoad.cpp b/nitrogen/generated/android/NitroTextOnLoad.cpp index a558792..9290e23 100644 --- a/nitrogen/generated/android/NitroTextOnLoad.cpp +++ b/nitrogen/generated/android/NitroTextOnLoad.cpp @@ -16,8 +16,8 @@ #include #include "JHybridNitroTextSpec.hpp" -#include "JFunc_void_TextLayoutEvent.hpp" #include "JFunc_void.hpp" +#include "JFunc_void_TextLayoutEvent.hpp" #include "views/JHybridNitroTextStateUpdater.hpp" #include @@ -31,15 +31,15 @@ int initialize(JavaVM* vm) { return facebook::jni::initialize(vm, [] { // Register native JNI methods margelo::nitro::nitrotext::JHybridNitroTextSpec::registerNatives(); - margelo::nitro::nitrotext::JFunc_void_TextLayoutEvent_cxx::registerNatives(); margelo::nitro::nitrotext::JFunc_void_cxx::registerNatives(); + margelo::nitro::nitrotext::JFunc_void_TextLayoutEvent_cxx::registerNatives(); margelo::nitro::nitrotext::views::JHybridNitroTextStateUpdater::registerNatives(); // Register Nitro Hybrid Objects HybridObjectRegistry::registerHybridObjectConstructor( "NitroText", []() -> std::shared_ptr { - static DefaultConstructableObject object("com/nitrotext/HybridNitroText"); + static DefaultConstructableObject object("com/margelo/nitro/nitrotext/HybridNitroText"); auto instance = object.create(); return instance->cthis()->shared(); } diff --git a/nitrogen/generated/android/c++/JFragment.hpp b/nitrogen/generated/android/c++/JFragment.hpp index caaee25..b79c6d0 100644 --- a/nitrogen/generated/android/c++/JFragment.hpp +++ b/nitrogen/generated/android/c++/JFragment.hpp @@ -74,6 +74,8 @@ namespace margelo::nitro::nitrotext { jni::local_ref textDecorationColor = this->getFieldValue(fieldTextDecorationColor); static const auto fieldTextDecorationStyle = clazz->getField("textDecorationStyle"); jni::local_ref textDecorationStyle = this->getFieldValue(fieldTextDecorationStyle); + static const auto fieldLinkUrl = clazz->getField("linkUrl"); + jni::local_ref linkUrl = this->getFieldValue(fieldLinkUrl); return Fragment( text != nullptr ? std::make_optional(text->toStdString()) : std::nullopt, selectionColor != nullptr ? std::make_optional(selectionColor->toStdString()) : std::nullopt, @@ -89,7 +91,8 @@ namespace margelo::nitro::nitrotext { textTransform != nullptr ? std::make_optional(textTransform->toCpp()) : std::nullopt, textDecorationLine != nullptr ? std::make_optional(textDecorationLine->toCpp()) : std::nullopt, textDecorationColor != nullptr ? std::make_optional(textDecorationColor->toStdString()) : std::nullopt, - textDecorationStyle != nullptr ? std::make_optional(textDecorationStyle->toCpp()) : std::nullopt + textDecorationStyle != nullptr ? std::make_optional(textDecorationStyle->toCpp()) : std::nullopt, + linkUrl != nullptr ? std::make_optional(linkUrl->toStdString()) : std::nullopt ); } @@ -99,7 +102,11 @@ namespace margelo::nitro::nitrotext { */ [[maybe_unused]] static jni::local_ref fromCpp(const Fragment& value) { - return newInstance( + using JSignature = JFragment(jni::alias_ref, jni::alias_ref, jni::alias_ref, jni::alias_ref, jni::alias_ref, jni::alias_ref, jni::alias_ref, jni::alias_ref, jni::alias_ref, jni::alias_ref, jni::alias_ref, jni::alias_ref, jni::alias_ref, jni::alias_ref, jni::alias_ref, jni::alias_ref); + static const auto clazz = javaClassStatic(); + static const auto create = clazz->getStaticMethod("fromCpp"); + return create( + clazz, value.text.has_value() ? jni::make_jstring(value.text.value()) : nullptr, value.selectionColor.has_value() ? jni::make_jstring(value.selectionColor.value()) : nullptr, value.fontSize.has_value() ? jni::JDouble::valueOf(value.fontSize.value()) : nullptr, @@ -114,7 +121,8 @@ namespace margelo::nitro::nitrotext { value.textTransform.has_value() ? JTextTransform::fromCpp(value.textTransform.value()) : nullptr, value.textDecorationLine.has_value() ? JTextDecorationLine::fromCpp(value.textDecorationLine.value()) : nullptr, value.textDecorationColor.has_value() ? jni::make_jstring(value.textDecorationColor.value()) : nullptr, - value.textDecorationStyle.has_value() ? JTextDecorationStyle::fromCpp(value.textDecorationStyle.value()) : nullptr + value.textDecorationStyle.has_value() ? JTextDecorationStyle::fromCpp(value.textDecorationStyle.value()) : nullptr, + value.linkUrl.has_value() ? jni::make_jstring(value.linkUrl.value()) : nullptr ); } }; diff --git a/nitrogen/generated/android/c++/JFunc_void.hpp b/nitrogen/generated/android/c++/JFunc_void.hpp index fab8e71..f1b7802 100644 --- a/nitrogen/generated/android/c++/JFunc_void.hpp +++ b/nitrogen/generated/android/c++/JFunc_void.hpp @@ -11,6 +11,7 @@ #include #include +#include namespace margelo::nitro::nitrotext { @@ -37,7 +38,7 @@ namespace margelo::nitro::nitrotext { /** * An implementation of Func_void that is backed by a C++ implementation (using `std::function<...>`) */ - struct JFunc_void_cxx final: public jni::HybridClass { + class JFunc_void_cxx final: public jni::HybridClass { public: static jni::local_ref fromCpp(const std::function& func) { return JFunc_void_cxx::newObjectCxxArgs(func); diff --git a/nitrogen/generated/android/c++/JFunc_void_TextLayoutEvent.hpp b/nitrogen/generated/android/c++/JFunc_void_TextLayoutEvent.hpp index c1ceac9..b03ff0c 100644 --- a/nitrogen/generated/android/c++/JFunc_void_TextLayoutEvent.hpp +++ b/nitrogen/generated/android/c++/JFunc_void_TextLayoutEvent.hpp @@ -12,6 +12,7 @@ #include "TextLayoutEvent.hpp" #include +#include #include "JTextLayoutEvent.hpp" #include "TextLayout.hpp" #include @@ -43,7 +44,7 @@ namespace margelo::nitro::nitrotext { /** * An implementation of Func_void_TextLayoutEvent that is backed by a C++ implementation (using `std::function<...>`) */ - struct JFunc_void_TextLayoutEvent_cxx final: public jni::HybridClass { + class JFunc_void_TextLayoutEvent_cxx final: public jni::HybridClass { public: static jni::local_ref fromCpp(const std::function& func) { return JFunc_void_TextLayoutEvent_cxx::newObjectCxxArgs(func); diff --git a/nitrogen/generated/android/c++/JHybridNitroTextSpec.cpp b/nitrogen/generated/android/c++/JHybridNitroTextSpec.cpp index 9421793..f7680ee 100644 --- a/nitrogen/generated/android/c++/JHybridNitroTextSpec.cpp +++ b/nitrogen/generated/android/c++/JHybridNitroTextSpec.cpp @@ -21,18 +21,16 @@ namespace margelo::nitro::nitrotext { enum class TextTransform; } namespace margelo::nitro::nitrotext { enum class TextDecorationLine; } // Forward declaration of `TextDecorationStyle` to properly resolve imports. namespace margelo::nitro::nitrotext { enum class TextDecorationStyle; } -// Forward declaration of `NitroRenderer` to properly resolve imports. -namespace margelo::nitro::nitrotext { enum class NitroRenderer; } -// Forward declaration of `RichTextStyleRule` to properly resolve imports. -namespace margelo::nitro::nitrotext { struct RichTextStyleRule; } -// Forward declaration of `RichTextStyle` to properly resolve imports. -namespace margelo::nitro::nitrotext { struct RichTextStyle; } +// Forward declaration of `Renderer` to properly resolve imports. +namespace margelo::nitro::nitrotext { enum class Renderer; } // Forward declaration of `EllipsizeMode` to properly resolve imports. namespace margelo::nitro::nitrotext { enum class EllipsizeMode; } // Forward declaration of `LineBreakStrategyIOS` to properly resolve imports. namespace margelo::nitro::nitrotext { enum class LineBreakStrategyIOS; } // Forward declaration of `DynamicTypeRamp` to properly resolve imports. namespace margelo::nitro::nitrotext { enum class DynamicTypeRamp; } +// Forward declaration of `MenuItem` to properly resolve imports. +namespace margelo::nitro::nitrotext { struct MenuItem; } // Forward declaration of `TextLayoutEvent` to properly resolve imports. namespace margelo::nitro::nitrotext { struct TextLayoutEvent; } // Forward declaration of `TextLayout` to properly resolve imports. @@ -55,25 +53,24 @@ namespace margelo::nitro::nitrotext { struct TextLayout; } #include "JTextDecorationLine.hpp" #include "TextDecorationStyle.hpp" #include "JTextDecorationStyle.hpp" -#include "NitroRenderer.hpp" -#include "JNitroRenderer.hpp" -#include "RichTextStyleRule.hpp" -#include "JRichTextStyleRule.hpp" -#include "RichTextStyle.hpp" -#include "JRichTextStyle.hpp" +#include "Renderer.hpp" +#include "JRenderer.hpp" #include "EllipsizeMode.hpp" #include "JEllipsizeMode.hpp" #include "LineBreakStrategyIOS.hpp" #include "JLineBreakStrategyIOS.hpp" #include "DynamicTypeRamp.hpp" #include "JDynamicTypeRamp.hpp" -#include "TextLayoutEvent.hpp" +#include "MenuItem.hpp" +#include "JMenuItem.hpp" #include +#include "JFunc_void.hpp" +#include +#include "TextLayoutEvent.hpp" #include "JFunc_void_TextLayoutEvent.hpp" #include "JTextLayoutEvent.hpp" #include "TextLayout.hpp" #include "JTextLayout.hpp" -#include "JFunc_void.hpp" namespace margelo::nitro::nitrotext { @@ -97,6 +94,12 @@ namespace margelo::nitro::nitrotext { method(_javaPart); } + std::string JHybridNitroTextSpec::toString() { + static const auto method = javaClassStatic()->getMethod("toString"); + auto javaString = method(_javaPart); + return javaString->toStdString(); + } + // Properties std::optional> JHybridNitroTextSpec::getFragments() { static const auto method = javaClassStatic()->getMethod>()>("getFragments"); @@ -119,45 +122,20 @@ namespace margelo::nitro::nitrotext { jni::local_ref> __array = jni::JArrayClass::newArray(__size); for (size_t __i = 0; __i < __size; __i++) { const auto& __element = fragments.value()[__i]; - __array->setElement(__i, *JFragment::fromCpp(__element)); + auto __elementJni = JFragment::fromCpp(__element); + __array->setElement(__i, *__elementJni); } return __array; }() : nullptr); } - std::optional JHybridNitroTextSpec::getRenderer() { - static const auto method = javaClassStatic()->getMethod()>("getRenderer"); + std::optional JHybridNitroTextSpec::getRenderer() { + static const auto method = javaClassStatic()->getMethod()>("getRenderer"); auto __result = method(_javaPart); return __result != nullptr ? std::make_optional(__result->toCpp()) : std::nullopt; } - void JHybridNitroTextSpec::setRenderer(std::optional renderer) { - static const auto method = javaClassStatic()->getMethod /* renderer */)>("setRenderer"); - method(_javaPart, renderer.has_value() ? JNitroRenderer::fromCpp(renderer.value()) : nullptr); - } - std::optional> JHybridNitroTextSpec::getRichTextStyleRules() { - static const auto method = javaClassStatic()->getMethod>()>("getRichTextStyleRules"); - auto __result = method(_javaPart); - return __result != nullptr ? std::make_optional([&]() { - size_t __size = __result->size(); - std::vector __vector; - __vector.reserve(__size); - for (size_t __i = 0; __i < __size; __i++) { - auto __element = __result->getElement(__i); - __vector.push_back(__element->toCpp()); - } - return __vector; - }()) : std::nullopt; - } - void JHybridNitroTextSpec::setRichTextStyleRules(const std::optional>& richTextStyleRules) { - static const auto method = javaClassStatic()->getMethod> /* richTextStyleRules */)>("setRichTextStyleRules"); - method(_javaPart, richTextStyleRules.has_value() ? [&]() { - size_t __size = richTextStyleRules.value().size(); - jni::local_ref> __array = jni::JArrayClass::newArray(__size); - for (size_t __i = 0; __i < __size; __i++) { - const auto& __element = richTextStyleRules.value()[__i]; - __array->setElement(__i, *JRichTextStyleRule::fromCpp(__element)); - } - return __array; - }() : nullptr); + void JHybridNitroTextSpec::setRenderer(std::optional renderer) { + static const auto method = javaClassStatic()->getMethod /* renderer */)>("setRenderer"); + method(_javaPart, renderer.has_value() ? JRenderer::fromCpp(renderer.value()) : nullptr); } std::optional JHybridNitroTextSpec::getSelectable() { static const auto method = javaClassStatic()->getMethod()>("getSelectable"); @@ -240,6 +218,33 @@ namespace margelo::nitro::nitrotext { static const auto method = javaClassStatic()->getMethod /* minimumFontScale */)>("setMinimumFontScale"); method(_javaPart, minimumFontScale.has_value() ? jni::JDouble::valueOf(minimumFontScale.value()) : nullptr); } + std::optional> JHybridNitroTextSpec::getMenus() { + static const auto method = javaClassStatic()->getMethod>()>("getMenus"); + auto __result = method(_javaPart); + return __result != nullptr ? std::make_optional([&]() { + size_t __size = __result->size(); + std::vector __vector; + __vector.reserve(__size); + for (size_t __i = 0; __i < __size; __i++) { + auto __element = __result->getElement(__i); + __vector.push_back(__element->toCpp()); + } + return __vector; + }()) : std::nullopt; + } + void JHybridNitroTextSpec::setMenus(const std::optional>& menus) { + static const auto method = javaClassStatic()->getMethod> /* menus */)>("setMenus"); + method(_javaPart, menus.has_value() ? [&]() { + size_t __size = menus.value().size(); + jni::local_ref> __array = jni::JArrayClass::newArray(__size); + for (size_t __i = 0; __i < __size; __i++) { + const auto& __element = menus.value()[__i]; + auto __elementJni = JMenuItem::fromCpp(__element); + __array->setElement(__i, *__elementJni); + } + return __array; + }() : nullptr); + } std::optional> JHybridNitroTextSpec::getOnTextLayout() { static const auto method = javaClassStatic()->getMethod()>("getOnTextLayout_cxx"); auto __result = method(_javaPart); @@ -249,9 +254,7 @@ namespace margelo::nitro::nitrotext { return downcast->cthis()->getFunction(); } else { auto __resultRef = jni::make_global(__result); - return [__resultRef](TextLayoutEvent layout) -> void { - return __resultRef->invoke(layout); - }; + return JNICallable(std::move(__resultRef)); } }()) : std::nullopt; } @@ -268,9 +271,7 @@ namespace margelo::nitro::nitrotext { return downcast->cthis()->getFunction(); } else { auto __resultRef = jni::make_global(__result); - return [__resultRef]() -> void { - return __resultRef->invoke(); - }; + return JNICallable(std::move(__resultRef)); } }()) : std::nullopt; } @@ -287,9 +288,7 @@ namespace margelo::nitro::nitrotext { return downcast->cthis()->getFunction(); } else { auto __resultRef = jni::make_global(__result); - return [__resultRef]() -> void { - return __resultRef->invoke(); - }; + return JNICallable(std::move(__resultRef)); } }()) : std::nullopt; } @@ -306,9 +305,7 @@ namespace margelo::nitro::nitrotext { return downcast->cthis()->getFunction(); } else { auto __resultRef = jni::make_global(__result); - return [__resultRef]() -> void { - return __resultRef->invoke(); - }; + return JNICallable(std::move(__resultRef)); } }()) : std::nullopt; } diff --git a/nitrogen/generated/android/c++/JHybridNitroTextSpec.hpp b/nitrogen/generated/android/c++/JHybridNitroTextSpec.hpp index 90196c2..996d9d4 100644 --- a/nitrogen/generated/android/c++/JHybridNitroTextSpec.hpp +++ b/nitrogen/generated/android/c++/JHybridNitroTextSpec.hpp @@ -41,6 +41,7 @@ namespace margelo::nitro::nitrotext { public: size_t getExternalMemorySize() noexcept override; void dispose() noexcept override; + std::string toString() override; public: inline const jni::global_ref& getJavaPart() const noexcept { @@ -51,10 +52,8 @@ namespace margelo::nitro::nitrotext { // Properties std::optional> getFragments() override; void setFragments(const std::optional>& fragments) override; - std::optional getRenderer() override; - void setRenderer(std::optional renderer) override; - std::optional> getRichTextStyleRules() override; - void setRichTextStyleRules(const std::optional>& richTextStyleRules) override; + std::optional getRenderer() override; + void setRenderer(std::optional renderer) override; std::optional getSelectable() override; void setSelectable(std::optional selectable) override; std::optional getAllowFontScaling() override; @@ -73,6 +72,8 @@ namespace margelo::nitro::nitrotext { void setAdjustsFontSizeToFit(std::optional adjustsFontSizeToFit) override; std::optional getMinimumFontScale() override; void setMinimumFontScale(std::optional minimumFontScale) override; + std::optional> getMenus() override; + void setMenus(const std::optional>& menus) override; std::optional> getOnTextLayout() override; void setOnTextLayout(const std::optional>& onTextLayout) override; std::optional> getOnPress() override; diff --git a/nitrogen/generated/android/c++/JMenuItem.hpp b/nitrogen/generated/android/c++/JMenuItem.hpp new file mode 100644 index 0000000..fcae031 --- /dev/null +++ b/nitrogen/generated/android/c++/JMenuItem.hpp @@ -0,0 +1,72 @@ +/// +/// JMenuItem.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#pragma once + +#include +#include "MenuItem.hpp" + +#include "JFunc_void.hpp" +#include +#include +#include + +namespace margelo::nitro::nitrotext { + + using namespace facebook; + + /** + * The C++ JNI bridge between the C++ struct "MenuItem" and the the Kotlin data class "MenuItem". + */ + struct JMenuItem final: public jni::JavaClass { + public: + static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/nitrotext/MenuItem;"; + + public: + /** + * Convert this Java/Kotlin-based struct to the C++ struct MenuItem by copying all values to C++. + */ + [[maybe_unused]] + [[nodiscard]] + MenuItem toCpp() const { + static const auto clazz = javaClassStatic(); + static const auto fieldTitle = clazz->getField("title"); + jni::local_ref title = this->getFieldValue(fieldTitle); + static const auto fieldAction = clazz->getField("action"); + jni::local_ref action = this->getFieldValue(fieldAction); + return MenuItem( + title->toStdString(), + [&]() -> std::function { + if (action->isInstanceOf(JFunc_void_cxx::javaClassStatic())) [[likely]] { + auto downcast = jni::static_ref_cast(action); + return downcast->cthis()->getFunction(); + } else { + auto actionRef = jni::make_global(action); + return JNICallable(std::move(actionRef)); + } + }() + ); + } + + public: + /** + * Create a Java/Kotlin-based struct by copying all values from the given C++ struct to Java. + */ + [[maybe_unused]] + static jni::local_ref fromCpp(const MenuItem& value) { + using JSignature = JMenuItem(jni::alias_ref, jni::alias_ref); + static const auto clazz = javaClassStatic(); + static const auto create = clazz->getStaticMethod("fromCpp"); + return create( + clazz, + jni::make_jstring(value.title), + JFunc_void_cxx::fromCpp(value.action) + ); + } + }; + +} // namespace margelo::nitro::nitrotext diff --git a/nitrogen/generated/android/c++/JTextLayout.hpp b/nitrogen/generated/android/c++/JTextLayout.hpp index 0224ce2..d323c2d 100644 --- a/nitrogen/generated/android/c++/JTextLayout.hpp +++ b/nitrogen/generated/android/c++/JTextLayout.hpp @@ -68,7 +68,11 @@ namespace margelo::nitro::nitrotext { */ [[maybe_unused]] static jni::local_ref fromCpp(const TextLayout& value) { - return newInstance( + using JSignature = JTextLayout(jni::alias_ref, double, double, double, double, double, double, double, double); + static const auto clazz = javaClassStatic(); + static const auto create = clazz->getStaticMethod("fromCpp"); + return create( + clazz, jni::make_jstring(value.text), value.x, value.y, diff --git a/nitrogen/generated/android/c++/JTextLayoutEvent.hpp b/nitrogen/generated/android/c++/JTextLayoutEvent.hpp index feb6cf0..a3ae4f9 100644 --- a/nitrogen/generated/android/c++/JTextLayoutEvent.hpp +++ b/nitrogen/generated/android/c++/JTextLayoutEvent.hpp @@ -56,13 +56,18 @@ namespace margelo::nitro::nitrotext { */ [[maybe_unused]] static jni::local_ref fromCpp(const TextLayoutEvent& value) { - return newInstance( + using JSignature = JTextLayoutEvent(jni::alias_ref>); + static const auto clazz = javaClassStatic(); + static const auto create = clazz->getStaticMethod("fromCpp"); + return create( + clazz, [&]() { size_t __size = value.lines.size(); jni::local_ref> __array = jni::JArrayClass::newArray(__size); for (size_t __i = 0; __i < __size; __i++) { const auto& __element = value.lines[__i]; - __array->setElement(__i, *JTextLayout::fromCpp(__element)); + auto __elementJni = JTextLayout::fromCpp(__element); + __array->setElement(__i, *__elementJni); } return __array; }() diff --git a/nitrogen/generated/android/c++/views/JHybridNitroTextStateUpdater.cpp b/nitrogen/generated/android/c++/views/JHybridNitroTextStateUpdater.cpp index b9ed656..00c4e4a 100644 --- a/nitrogen/generated/android/c++/views/JHybridNitroTextStateUpdater.cpp +++ b/nitrogen/generated/android/c++/views/JHybridNitroTextStateUpdater.cpp @@ -44,10 +44,6 @@ void JHybridNitroTextStateUpdater::updateViewProps(jni::alias_ref / view->setRenderer(props.renderer.value); // TODO: Set isDirty = false } - if (props.richTextStyleRules.isDirty) { - view->setRichTextStyleRules(props.richTextStyleRules.value); - // TODO: Set isDirty = false - } if (props.selectable.isDirty) { view->setSelectable(props.selectable.value); // TODO: Set isDirty = false @@ -84,6 +80,10 @@ void JHybridNitroTextStateUpdater::updateViewProps(jni::alias_ref / view->setMinimumFontScale(props.minimumFontScale.value); // TODO: Set isDirty = false } + if (props.menus.isDirty) { + view->setMenus(props.menus.value); + // TODO: Set isDirty = false + } if (props.onTextLayout.isDirty) { view->setOnTextLayout(props.onTextLayout.value); // TODO: Set isDirty = false diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/Fragment.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/Fragment.kt index 34e46a1..e693831 100644 --- a/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/Fragment.kt +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/Fragment.kt @@ -9,7 +9,6 @@ package com.margelo.nitro.nitrotext import androidx.annotation.Keep import com.facebook.proguard.annotations.DoNotStrip -import com.margelo.nitro.core.* /** @@ -17,55 +16,68 @@ import com.margelo.nitro.core.* */ @DoNotStrip @Keep -data class Fragment +data class Fragment( @DoNotStrip @Keep - constructor( - @DoNotStrip - @Keep - val text: String?, - @DoNotStrip - @Keep - val selectionColor: String?, - @DoNotStrip - @Keep - val fontSize: Double?, - @DoNotStrip - @Keep - val fontWeight: FontWeight?, - @DoNotStrip - @Keep - val fontColor: String?, - @DoNotStrip - @Keep - val fragmentBackgroundColor: String?, - @DoNotStrip - @Keep - val fontStyle: FontStyle?, - @DoNotStrip - @Keep - val fontFamily: String?, - @DoNotStrip - @Keep - val lineHeight: Double?, - @DoNotStrip - @Keep - val letterSpacing: Double?, - @DoNotStrip - @Keep - val textAlign: TextAlign?, - @DoNotStrip - @Keep - val textTransform: TextTransform?, - @DoNotStrip - @Keep - val textDecorationLine: TextDecorationLine?, - @DoNotStrip - @Keep - val textDecorationColor: String?, + val text: String?, + @DoNotStrip + @Keep + val selectionColor: String?, + @DoNotStrip + @Keep + val fontSize: Double?, + @DoNotStrip + @Keep + val fontWeight: FontWeight?, + @DoNotStrip + @Keep + val fontColor: String?, + @DoNotStrip + @Keep + val fragmentBackgroundColor: String?, + @DoNotStrip + @Keep + val fontStyle: FontStyle?, + @DoNotStrip + @Keep + val fontFamily: String?, + @DoNotStrip + @Keep + val lineHeight: Double?, + @DoNotStrip + @Keep + val letterSpacing: Double?, + @DoNotStrip + @Keep + val textAlign: TextAlign?, + @DoNotStrip + @Keep + val textTransform: TextTransform?, + @DoNotStrip + @Keep + val textDecorationLine: TextDecorationLine?, + @DoNotStrip + @Keep + val textDecorationColor: String?, + @DoNotStrip + @Keep + val textDecorationStyle: TextDecorationStyle?, + @DoNotStrip + @Keep + val linkUrl: String? +) { + /* primary constructor */ + + private companion object { + /** + * Constructor called from C++ + */ @DoNotStrip @Keep - val textDecorationStyle: TextDecorationStyle? - ) { - /* main constructor */ + @Suppress("unused") + @JvmStatic + private fun fromCpp(text: String?, selectionColor: String?, fontSize: Double?, fontWeight: FontWeight?, fontColor: String?, fragmentBackgroundColor: String?, fontStyle: FontStyle?, fontFamily: String?, lineHeight: Double?, letterSpacing: Double?, textAlign: TextAlign?, textTransform: TextTransform?, textDecorationLine: TextDecorationLine?, textDecorationColor: String?, textDecorationStyle: TextDecorationStyle?, linkUrl: String?): Fragment { + return Fragment(text, selectionColor, fontSize, fontWeight, fontColor, fragmentBackgroundColor, fontStyle, fontFamily, lineHeight, letterSpacing, textAlign, textTransform, textDecorationLine, textDecorationColor, textDecorationStyle, linkUrl) + } + } } diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/Func_void.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/Func_void.kt index 63db75b..cb01e75 100644 --- a/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/Func_void.kt +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/Func_void.kt @@ -10,7 +10,6 @@ package com.margelo.nitro.nitrotext import androidx.annotation.Keep import com.facebook.jni.HybridData import com.facebook.proguard.annotations.DoNotStrip -import com.margelo.nitro.core.* import dalvik.annotation.optimization.FastNative diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/Func_void_TextLayoutEvent.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/Func_void_TextLayoutEvent.kt index 8faef9b..3534207 100644 --- a/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/Func_void_TextLayoutEvent.kt +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/Func_void_TextLayoutEvent.kt @@ -10,7 +10,6 @@ package com.margelo.nitro.nitrotext import androidx.annotation.Keep import com.facebook.jni.HybridData import com.facebook.proguard.annotations.DoNotStrip -import com.margelo.nitro.core.* import dalvik.annotation.optimization.FastNative diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/HybridNitroTextSpec.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/HybridNitroTextSpec.kt index 4984833..ceb2d86 100644 --- a/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/HybridNitroTextSpec.kt +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/HybridNitroTextSpec.kt @@ -10,8 +10,7 @@ package com.margelo.nitro.nitrotext import androidx.annotation.Keep import com.facebook.jni.HybridData import com.facebook.proguard.annotations.DoNotStrip -import com.margelo.nitro.core.* -import com.margelo.nitro.views.* +import com.margelo.nitro.views.HybridView /** * A Kotlin class representing the NitroText HybridObject. @@ -37,6 +36,11 @@ abstract class HybridNitroTextSpec: HybridView() { super.updateNative(hybridData) } + // Default implementation of `HybridObject.toString()` + override fun toString(): String { + return "[HybridObject NitroText]" + } + // Properties @get:DoNotStrip @get:Keep @@ -48,13 +52,7 @@ abstract class HybridNitroTextSpec: HybridView() { @get:Keep @set:DoNotStrip @set:Keep - abstract var renderer: NitroRenderer? - - @get:DoNotStrip - @get:Keep - @set:DoNotStrip - @set:Keep - abstract var richTextStyleRules: Array? + abstract var renderer: Renderer? @get:DoNotStrip @get:Keep @@ -110,6 +108,12 @@ abstract class HybridNitroTextSpec: HybridView() { @set:Keep abstract var minimumFontScale: Double? + @get:DoNotStrip + @get:Keep + @set:DoNotStrip + @set:Keep + abstract var menus: Array? + abstract var onTextLayout: ((layout: TextLayoutEvent) -> Unit)? private var onTextLayout_cxx: Func_void_TextLayoutEvent? @@ -262,6 +266,6 @@ abstract class HybridNitroTextSpec: HybridView() { private external fun initHybrid(): HybridData companion object { - private const val TAG = "HybridNitroTextSpec" + protected const val TAG = "HybridNitroTextSpec" } } diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/MenuItem.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/MenuItem.kt new file mode 100644 index 0000000..b9ef971 --- /dev/null +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/MenuItem.kt @@ -0,0 +1,45 @@ +/// +/// MenuItem.kt +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +package com.margelo.nitro.nitrotext + +import androidx.annotation.Keep +import com.facebook.proguard.annotations.DoNotStrip + + +/** + * Represents the JavaScript object/struct "MenuItem". + */ +@DoNotStrip +@Keep +data class MenuItem( + @DoNotStrip + @Keep + val title: String, + @DoNotStrip + @Keep + val action: Func_void +) { + /** + * Create a new instance of MenuItem from Kotlin + */ + constructor(title: String, action: () -> Unit): + this(title, Func_void_java(action)) + + private companion object { + /** + * Constructor called from C++ + */ + @DoNotStrip + @Keep + @Suppress("unused") + @JvmStatic + private fun fromCpp(title: String, action: Func_void): MenuItem { + return MenuItem(title, action) + } + } +} diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/TextLayout.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/TextLayout.kt index 7866d8f..dc2c49b 100644 --- a/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/TextLayout.kt +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/TextLayout.kt @@ -9,7 +9,6 @@ package com.margelo.nitro.nitrotext import androidx.annotation.Keep import com.facebook.proguard.annotations.DoNotStrip -import com.margelo.nitro.core.* /** @@ -17,37 +16,47 @@ import com.margelo.nitro.core.* */ @DoNotStrip @Keep -data class TextLayout +data class TextLayout( @DoNotStrip @Keep - constructor( - @DoNotStrip - @Keep - val text: String, - @DoNotStrip - @Keep - val x: Double, - @DoNotStrip - @Keep - val y: Double, - @DoNotStrip - @Keep - val width: Double, - @DoNotStrip - @Keep - val height: Double, - @DoNotStrip - @Keep - val descender: Double, - @DoNotStrip - @Keep - val capHeight: Double, - @DoNotStrip - @Keep - val ascender: Double, - @DoNotStrip - @Keep - val xHeight: Double - ) { - /* main constructor */ + val text: String, + @DoNotStrip + @Keep + val x: Double, + @DoNotStrip + @Keep + val y: Double, + @DoNotStrip + @Keep + val width: Double, + @DoNotStrip + @Keep + val height: Double, + @DoNotStrip + @Keep + val descender: Double, + @DoNotStrip + @Keep + val capHeight: Double, + @DoNotStrip + @Keep + val ascender: Double, + @DoNotStrip + @Keep + val xHeight: Double +) { + /* primary constructor */ + + private companion object { + /** + * Constructor called from C++ + */ + @DoNotStrip + @Keep + @Suppress("unused") + @JvmStatic + private fun fromCpp(text: String, x: Double, y: Double, width: Double, height: Double, descender: Double, capHeight: Double, ascender: Double, xHeight: Double): TextLayout { + return TextLayout(text, x, y, width, height, descender, capHeight, ascender, xHeight) + } + } } diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/TextLayoutEvent.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/TextLayoutEvent.kt index 90b29ff..bbc7b01 100644 --- a/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/TextLayoutEvent.kt +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/TextLayoutEvent.kt @@ -9,7 +9,6 @@ package com.margelo.nitro.nitrotext import androidx.annotation.Keep import com.facebook.proguard.annotations.DoNotStrip -import com.margelo.nitro.core.* /** @@ -17,13 +16,23 @@ import com.margelo.nitro.core.* */ @DoNotStrip @Keep -data class TextLayoutEvent +data class TextLayoutEvent( @DoNotStrip @Keep - constructor( + val lines: Array +) { + /* primary constructor */ + + private companion object { + /** + * Constructor called from C++ + */ @DoNotStrip @Keep - val lines: Array - ) { - /* main constructor */ + @Suppress("unused") + @JvmStatic + private fun fromCpp(lines: Array): TextLayoutEvent { + return TextLayoutEvent(lines) + } + } } diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/views/HybridNitroTextManager.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/views/HybridNitroTextManager.kt index 89cd355..ec18112 100644 --- a/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/views/HybridNitroTextManager.kt +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/views/HybridNitroTextManager.kt @@ -12,12 +12,12 @@ import com.facebook.react.uimanager.ReactStylesDiffMap import com.facebook.react.uimanager.SimpleViewManager import com.facebook.react.uimanager.StateWrapper import com.facebook.react.uimanager.ThemedReactContext -import com.nitrotext.* +import com.nitrotext.HybridNitroText /** * Represents the React Native `ViewManager` for the "NitroText" Nitro HybridView. */ -class HybridNitroTextManager: SimpleViewManager() { +open class HybridNitroTextManager: SimpleViewManager() { private val views = hashMapOf() override fun getName(): String { diff --git a/src/nitro-text.tsx b/src/nitro-text.tsx index 65dac79..1350467 100644 --- a/src/nitro-text.tsx +++ b/src/nitro-text.tsx @@ -93,7 +93,7 @@ export const NitroText = (props: NitroTextPropsWithEvents) => { [onTextLayout] ) - if (isInsideRNText || Platform.OS === 'android') { + if (isInsideRNText) { return ( Date: Sun, 23 Nov 2025 21:30:37 +0200 Subject: [PATCH 07/12] feat: add HTML rendering logic support - Remove old renderer system (NitroRenderer, RichTextStyle, RichTextStyleRule) - Add new Renderer type for HTML and plaintext rendering - Update renderer prop support in native implementations - HTML parsing is now handled in JS/TS layer - Add renderer bridge code for Android --- .../nitrotext/renderers/NitroHtmlRenderer.kt | 467 ------------------ .../renderers/NitroRendererInterface.kt | 9 - .../c++/{JNitroRenderer.hpp => JRenderer.hpp} | 28 +- .../generated/android/c++/JRichTextStyle.hpp | 130 ----- .../android/c++/JRichTextStyleRule.hpp | 72 --- .../{NitroRenderer.kt => Renderer.kt} | 10 +- .../margelo/nitro/nitrotext/RichTextStyle.kt | 77 --- .../nitro/nitrotext/RichTextStyleRule.kt | 32 -- .../generated/ios/swift/NitroRenderer.swift | 40 -- .../generated/ios/swift/RichTextStyle.swift | 443 ----------------- .../ios/swift/RichTextStyleRule.swift | 46 -- .../generated/shared/c++/NitroRenderer.hpp | 76 --- .../generated/shared/c++/RichTextStyle.hpp | 149 ------ .../shared/c++/RichTextStyleRule.hpp | 73 --- 14 files changed, 19 insertions(+), 1633 deletions(-) delete mode 100644 android/src/main/java/com/nitrotext/renderers/NitroHtmlRenderer.kt delete mode 100644 android/src/main/java/com/nitrotext/renderers/NitroRendererInterface.kt rename nitrogen/generated/android/c++/{JNitroRenderer.hpp => JRenderer.hpp} (64%) delete mode 100644 nitrogen/generated/android/c++/JRichTextStyle.hpp delete mode 100644 nitrogen/generated/android/c++/JRichTextStyleRule.hpp rename nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/{NitroRenderer.kt => Renderer.kt} (64%) delete mode 100644 nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/RichTextStyle.kt delete mode 100644 nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/RichTextStyleRule.kt delete mode 100644 nitrogen/generated/ios/swift/NitroRenderer.swift delete mode 100644 nitrogen/generated/ios/swift/RichTextStyle.swift delete mode 100644 nitrogen/generated/ios/swift/RichTextStyleRule.swift delete mode 100644 nitrogen/generated/shared/c++/NitroRenderer.hpp delete mode 100644 nitrogen/generated/shared/c++/RichTextStyle.hpp delete mode 100644 nitrogen/generated/shared/c++/RichTextStyleRule.hpp diff --git a/android/src/main/java/com/nitrotext/renderers/NitroHtmlRenderer.kt b/android/src/main/java/com/nitrotext/renderers/NitroHtmlRenderer.kt deleted file mode 100644 index f680bc7..0000000 --- a/android/src/main/java/com/nitrotext/renderers/NitroHtmlRenderer.kt +++ /dev/null @@ -1,467 +0,0 @@ -package com.nitrotext.renderers - -import android.content.Context -import android.graphics.Typeface -import android.text.SpannableStringBuilder -import android.text.Spanned -import android.text.style.AbsoluteSizeSpan -import android.text.style.BackgroundColorSpan -import android.text.style.ForegroundColorSpan -import android.text.style.StrikethroughSpan -import android.text.style.StyleSpan -import android.text.style.TypefaceSpan -import android.text.style.UnderlineSpan -import com.facebook.react.uimanager.PixelUtil -import com.margelo.nitro.nitrotext.FontStyle -import com.margelo.nitro.nitrotext.FontWeight -import com.margelo.nitro.nitrotext.RichTextStyle -import com.margelo.nitro.nitrotext.RichTextStyleRule -import com.margelo.nitro.nitrotext.TextAlign -import com.margelo.nitro.nitrotext.TextDecorationLine -import com.margelo.nitro.nitrotext.TextDecorationStyle -import com.margelo.nitro.nitrotext.TextTransform -import org.jsoup.Jsoup -import org.jsoup.nodes.Element -import org.jsoup.nodes.Node -import org.jsoup.nodes.TextNode -import java.util.Locale -import androidx.core.graphics.toColorInt -import com.nitrotext.spans.BulletListSpan -import com.nitrotext.spans.LetterSpacingSpan -import com.nitrotext.spans.NitroLineHeightSpan -import com.nitrotext.spans.NumberedListSpan -import com.nitrotext.spans.UrlSpanNoUnderline -import com.nitrotext.spans.VerticalMarginSpan - -/** - * Converts HTML markup to a SpannableStringBuilder while applying Nitro rich text styles. - */ -class NitroHtmlRenderer( - private val context: Context, - private val defaultTextSizePx: Float, - private val allowFontScaling: Boolean, - private val maxFontSizeMultiplier: Double? -) : NitroRendererInterface { - - private val listLeadingMarginPx = PixelUtil.toPixelFromDIP(24f).toInt() - private val listGapPx = PixelUtil.toPixelFromDIP(8f).toInt() - private val bulletRadiusPx = PixelUtil.toPixelFromDIP(3f) - - override fun render( - input: String, - baseStyle: RichTextStyle?, - rules: Array? - ): SpannableStringBuilder { - val builder = SpannableStringBuilder() - val document = Jsoup.parse(input) - val ruleMap = buildRuleMap(rules) - - val stateStack = ArrayDeque() - stateStack.addLast(StyleState(baseStyle ?: EMPTY_STYLE, preserveWhitespace = false)) - - val blockStack = ArrayDeque() - val blockRanges = mutableListOf() - val listStack = ArrayDeque() - - traverse(document.body(), builder, stateStack, blockStack, blockRanges, listStack, ruleMap) - - applyBlockMargins(builder, blockRanges) - return builder - } - - private fun traverse( - node: Node, - builder: SpannableStringBuilder, - stateStack: ArrayDeque, - blockStack: ArrayDeque, - blockRanges: MutableList, - listStack: ArrayDeque, - ruleMap: Map - ) { - when (node) { - is TextNode -> appendText(node, builder, stateStack.last()) - is Element -> handleElement(node, builder, stateStack, blockStack, blockRanges, listStack, ruleMap) - } - } - - private fun appendText(textNode: TextNode, builder: SpannableStringBuilder, state: StyleState) { - val raw = if (state.preserveWhitespace) textNode.wholeText else textNode.text() - if (raw.isEmpty()) return - - val content = applyTextTransform(raw, state.style.textTransform) - if (content.isEmpty()) return - - val start = builder.length - builder.append(content) - val end = builder.length - - val style = state.style - - style.fontColor?.let { parseColorSafe(it)?.let { c -> - builder.setSpan(ForegroundColorSpan(c), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) - } } - - style.fragmentBackgroundColor?.let { parseColorSafe(it)?.let { c -> - builder.setSpan(BackgroundColorSpan(c), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) - } } - - style.fontSize?.let { size -> - builder.setSpan(AbsoluteSizeSpan(size.toInt(), true), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) - } - - when (style.fontWeight) { - FontWeight.BOLD, FontWeight.SEMIBOLD, FontWeight.HEAVY, FontWeight.BLACK -> - builder.setSpan(StyleSpan(Typeface.BOLD), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) - FontWeight.ULTRALIGHT, - FontWeight.THIN, - FontWeight.LIGHT, - FontWeight.MEDIUM, - FontWeight.REGULAR, - FontWeight.CONDENSED, - FontWeight.CONDENSEDBOLD, - FontWeight.NORMAL, - null -> Unit - } - - when (style.fontStyle) { - FontStyle.ITALIC, FontStyle.OBLIQUE -> builder.setSpan(StyleSpan(Typeface.ITALIC), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) - FontStyle.NORMAL, null -> Unit - } - - style.fontFamily?.let { - builder.setSpan(TypefaceSpan(it), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) - } - - when (style.textDecorationLine) { - TextDecorationLine.UNDERLINE -> builder.setSpan(UnderlineSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) - TextDecorationLine.LINE_THROUGH -> builder.setSpan(StrikethroughSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) - TextDecorationLine.UNDERLINE_LINE_THROUGH -> { - builder.setSpan(UnderlineSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) - builder.setSpan(StrikethroughSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) - } - TextDecorationLine.NONE, null -> Unit - } - - style.lineHeight?.let { value -> - val px = resolveLineHeight(value) - if (px > 0f) builder.setSpan(NitroLineHeightSpan(px), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) - } - - style.letterSpacing?.let { spacing -> - val em = resolveLetterSpacing(spacing) - if (em != 0f) builder.setSpan(LetterSpacingSpan(em), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) - } - - state.linkHref?.let { url -> - builder.setSpan(UrlSpanNoUnderline(url), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) - } - } - - private fun handleElement( - element: Element, - builder: SpannableStringBuilder, - stateStack: ArrayDeque, - blockStack: ArrayDeque, - blockRanges: MutableList, - listStack: ArrayDeque, - ruleMap: Map - ) { - val tag = element.normalName().lowercase(Locale.US) - - if (tag == "br") { - builder.append('\n') - return - } - - val current = stateStack.last() - val ruleStyle = ruleMap[tag] - val tagStyle = defaultStyleForTag(tag) - val merged = mergeStyles(current.style, tagStyle) - val finalStyle = mergeStyles(merged, ruleStyle) - val linkHref = when (tag) { - "a" -> element.attr("href").takeIf { it.isNotEmpty() } ?: current.linkHref - else -> current.linkHref - } - - val preserveWhitespace = current.preserveWhitespace || tag == "pre" - - val isListContainer = tag == "ul" || tag == "ol" - if (isListContainer) { - listStack.addLast(ListContext(if (tag == "ol") ListType.ORDERED else ListType.UNORDERED)) - } - - val listContextForItem = if (tag == "li") listStack.lastOrNull() else null - val listMarkerIndex = if (listContextForItem?.type == ListType.ORDERED) { - listContextForItem.nextIndex() - } else null - - val isBlock = tag in BLOCK_TAGS || tag == "li" - val blockStartIndex = if (isBlock) beginBlock(builder) else -1 - val blockContext = if (isBlock) { - val top = finalStyle.marginTop?.let { toPx(it) } ?: 0f - val bottom = finalStyle.marginBottom?.let { toPx(it) } ?: 0f - BlockContext(blockStartIndex, top, bottom) - } else null - - blockContext?.let { blockStack.addLast(it) } - - val childState = StyleState(finalStyle, linkHref, preserveWhitespace) - stateStack.addLast(childState) - - val children = element.childNodes() - for (child in children) { - traverse(child, builder, stateStack, blockStack, blockRanges, listStack, ruleMap) - } - - stateStack.removeLast() - - var blockEnd = -1 - if (isBlock) { - val ctx = blockStack.removeLast() - blockEnd = endBlock(builder) - if (blockEnd > ctx.start) { - blockRanges.add(BlockRange(ctx.start, blockEnd, ctx.topMarginPx, ctx.bottomMarginPx)) - } - } - - if (tag == "li" && blockStartIndex >= 0 && blockEnd > blockStartIndex) { - applyListSpan( - builder = builder, - start = blockStartIndex, - end = blockEnd, - listType = listContextForItem?.type, - listIndex = listMarkerIndex, - depth = listStack.size - ) - } - - if (isListContainer && listStack.isNotEmpty()) { - listStack.removeLast() - } - } - - private fun beginBlock(builder: SpannableStringBuilder): Int { - ensureTrailingNewline(builder) - return builder.length - } - - private fun endBlock(builder: SpannableStringBuilder): Int { - val end = builder.length - ensureTrailingNewline(builder) - return end - } - - private fun ensureTrailingNewline(builder: SpannableStringBuilder) { - val length = builder.length - if (length == 0) return - var index = length - 1 - while (index >= 0 && builder[index] == ' ') index-- - if (index >= 0 && builder[index] != '\n') { - builder.append('\n') - } - } - - private fun applyBlockMargins(builder: SpannableStringBuilder, ranges: List) { - for (range in ranges) { - if (range.end <= range.start) continue - if (range.topMarginPx <= 0f && range.bottomMarginPx <= 0f) continue - builder.setSpan( - VerticalMarginSpan(range.topMarginPx, range.bottomMarginPx), - range.start, - range.end, - Spanned.SPAN_EXCLUSIVE_EXCLUSIVE - ) - } - } - - private fun applyListSpan( - builder: SpannableStringBuilder, - start: Int, - end: Int, - listType: ListType?, - listIndex: Int?, - depth: Int - ) { - if (listType == null || start >= end) return - val effectiveDepth = depth.coerceAtLeast(1) - val leadingMargin = listLeadingMarginPx * effectiveDepth - val span = when (listType) { - ListType.UNORDERED -> BulletListSpan(leadingMargin, listGapPx, bulletRadiusPx) - ListType.ORDERED -> NumberedListSpan(listIndex ?: 0, leadingMargin, listGapPx) - } - builder.setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) - } - - private fun mergeStyles(base: RichTextStyle, override: RichTextStyle?): RichTextStyle { - if (override == null) return base - return createStyle( - fontColor = override.fontColor ?: base.fontColor, - fragmentBackgroundColor = override.fragmentBackgroundColor ?: base.fragmentBackgroundColor, - fontSize = override.fontSize ?: base.fontSize, - fontWeight = override.fontWeight ?: base.fontWeight, - fontStyle = override.fontStyle ?: base.fontStyle, - fontFamily = override.fontFamily ?: base.fontFamily, - lineHeight = override.lineHeight ?: base.lineHeight, - letterSpacing = override.letterSpacing ?: base.letterSpacing, - textAlign = override.textAlign ?: base.textAlign, - textTransform = override.textTransform ?: base.textTransform, - textDecorationLine = mergeDecorations(base.textDecorationLine, override.textDecorationLine), - textDecorationColor = override.textDecorationColor ?: base.textDecorationColor, - textDecorationStyle = override.textDecorationStyle ?: base.textDecorationStyle, - marginTop = override.marginTop ?: base.marginTop, - marginBottom = override.marginBottom ?: base.marginBottom, - marginLeft = override.marginLeft ?: base.marginLeft, - marginRight = override.marginRight ?: base.marginRight, - ) - } - - private fun mergeDecorations(a: TextDecorationLine?, b: TextDecorationLine?): TextDecorationLine? { - if (b == null) return a - if (a == null) return b - if (a == b) return a - val underline = a == TextDecorationLine.UNDERLINE || a == TextDecorationLine.UNDERLINE_LINE_THROUGH || - b == TextDecorationLine.UNDERLINE || b == TextDecorationLine.UNDERLINE_LINE_THROUGH - val strike = a == TextDecorationLine.LINE_THROUGH || a == TextDecorationLine.UNDERLINE_LINE_THROUGH || - b == TextDecorationLine.LINE_THROUGH || b == TextDecorationLine.UNDERLINE_LINE_THROUGH - return when { - underline && strike -> TextDecorationLine.UNDERLINE_LINE_THROUGH - underline -> TextDecorationLine.UNDERLINE - strike -> TextDecorationLine.LINE_THROUGH - else -> TextDecorationLine.NONE - } - } - - private fun defaultStyleForTag(tag: String): RichTextStyle? = when (tag) { - "strong", "b" -> createStyle(fontWeight = FontWeight.BOLD) - "em", "i" -> createStyle(fontStyle = FontStyle.ITALIC) - "u" -> createStyle(textDecorationLine = TextDecorationLine.UNDERLINE) - "s", "strike", "del" -> createStyle(textDecorationLine = TextDecorationLine.LINE_THROUGH) - "code" -> createStyle(fontFamily = "monospace") - else -> null - } - - private fun buildRuleMap(rules: Array?): Map { - if (rules == null || rules.isEmpty()) return emptyMap() - val map = HashMap(rules.size) - for (rule in rules) { - map[rule.selector.lowercase(Locale.US)] = rule.style - } - return map - } - - private fun resolveLineHeight(value: Double): Float { - return if (allowFontScaling) { - PixelUtil.toPixelFromSP(value.toFloat(), maxFontSizeMultiplier?.toFloat() ?: Float.NaN) - } else { - PixelUtil.toPixelFromDIP(value.toFloat()) - } - } - - private fun resolveLetterSpacing(value: Double): Float { - val px = if (allowFontScaling) { - PixelUtil.toPixelFromSP(value.toFloat(), maxFontSizeMultiplier?.toFloat() ?: Float.NaN) - } else { - PixelUtil.toPixelFromDIP(value.toFloat()) - } - return if (defaultTextSizePx == 0f) 0f else px / defaultTextSizePx - } - - private fun applyTextTransform(text: String, transform: TextTransform?): String { - return when (transform) { - TextTransform.UPPERCASE -> text.uppercase() - TextTransform.LOWERCASE -> text.lowercase() - TextTransform.CAPITALIZE -> text.split(' ').joinToString(" ") { part -> - if (part.isEmpty()) part else part.replaceFirstChar { ch -> ch.titlecase() } - } - TextTransform.NONE, null -> text - } - } - - private fun parseColorSafe(value: String): Int? = try { - value.toColorInt() - } catch (_: Exception) { - null - } - - private fun toPx(value: Double): Float { - return PixelUtil.toPixelFromDIP(value.toFloat()) - } - - private enum class ListType { ORDERED, UNORDERED } - - private class ListContext(val type: ListType) { - private var next = 1 - fun nextIndex(): Int { - val current = next - next += 1 - return current - } - } - - private data class StyleState( - val style: RichTextStyle, - val linkHref: String? = null, - val preserveWhitespace: Boolean = false, - ) - - private data class BlockContext( - val start: Int, - val topMarginPx: Float, - val bottomMarginPx: Float, - ) - - private data class BlockRange( - val start: Int, - val end: Int, - val topMarginPx: Float, - val bottomMarginPx: Float, - ) - - companion object { - private val BLOCK_TAGS = setOf( - "p", "div", "section", "article", "header", "footer", "aside", - "h1", "h2", "h3", "h4", "h5", "h6", "blockquote", "pre" - ) - private val EMPTY_STYLE = createStyle() - - private fun createStyle( - fontColor: String? = null, - fragmentBackgroundColor: String? = null, - fontSize: Double? = null, - fontWeight: FontWeight? = null, - fontStyle: FontStyle? = null, - fontFamily: String? = null, - lineHeight: Double? = null, - letterSpacing: Double? = null, - textAlign: TextAlign? = null, - textTransform: TextTransform? = null, - textDecorationLine: TextDecorationLine? = null, - textDecorationColor: String? = null, - textDecorationStyle: TextDecorationStyle? = null, - marginTop: Double? = null, - marginBottom: Double? = null, - marginLeft: Double? = null, - marginRight: Double? = null, - ): RichTextStyle { - return RichTextStyle( - fontColor, - fragmentBackgroundColor, - fontSize, - fontWeight, - fontStyle, - fontFamily, - lineHeight, - letterSpacing, - textAlign, - textTransform, - textDecorationLine, - textDecorationColor, - textDecorationStyle, - marginTop, - marginBottom, - marginLeft, - marginRight, - ) - } - } -} diff --git a/android/src/main/java/com/nitrotext/renderers/NitroRendererInterface.kt b/android/src/main/java/com/nitrotext/renderers/NitroRendererInterface.kt deleted file mode 100644 index 0e88d34..0000000 --- a/android/src/main/java/com/nitrotext/renderers/NitroRendererInterface.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.nitrotext.renderers - -import android.text.SpannableStringBuilder -import com.margelo.nitro.nitrotext.RichTextStyle -import com.margelo.nitro.nitrotext.RichTextStyleRule - -interface NitroRendererInterface { - fun render(input: String, baseStyle: RichTextStyle?, rules: Array?): SpannableStringBuilder -} \ No newline at end of file diff --git a/nitrogen/generated/android/c++/JNitroRenderer.hpp b/nitrogen/generated/android/c++/JRenderer.hpp similarity index 64% rename from nitrogen/generated/android/c++/JNitroRenderer.hpp rename to nitrogen/generated/android/c++/JRenderer.hpp index 7350303..ef80c85 100644 --- a/nitrogen/generated/android/c++/JNitroRenderer.hpp +++ b/nitrogen/generated/android/c++/JRenderer.hpp @@ -1,5 +1,5 @@ /// -/// JNitroRenderer.hpp +/// JRenderer.hpp /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. /// https://github.com/mrousavy/nitro /// Copyright © 2025 Marc Rousavy @ Margelo @@ -8,30 +8,30 @@ #pragma once #include -#include "NitroRenderer.hpp" +#include "Renderer.hpp" namespace margelo::nitro::nitrotext { using namespace facebook; /** - * The C++ JNI bridge between the C++ enum "NitroRenderer" and the the Kotlin enum "NitroRenderer". + * The C++ JNI bridge between the C++ enum "Renderer" and the the Kotlin enum "Renderer". */ - struct JNitroRenderer final: public jni::JavaClass { + struct JRenderer final: public jni::JavaClass { public: - static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/nitrotext/NitroRenderer;"; + static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/nitrotext/Renderer;"; public: /** - * Convert this Java/Kotlin-based enum to the C++ enum NitroRenderer. + * Convert this Java/Kotlin-based enum to the C++ enum Renderer. */ [[maybe_unused]] [[nodiscard]] - NitroRenderer toCpp() const { + Renderer toCpp() const { static const auto clazz = javaClassStatic(); static const auto fieldOrdinal = clazz->getField("value"); int ordinal = this->getFieldValue(fieldOrdinal); - return static_cast(ordinal); + return static_cast(ordinal); } public: @@ -39,16 +39,16 @@ namespace margelo::nitro::nitrotext { * Create a Java/Kotlin-based enum with the given C++ enum's value. */ [[maybe_unused]] - static jni::alias_ref fromCpp(NitroRenderer value) { + static jni::alias_ref fromCpp(Renderer value) { static const auto clazz = javaClassStatic(); - static const auto fieldPLAINTEXT = clazz->getStaticField("PLAINTEXT"); - static const auto fieldHTML = clazz->getStaticField("HTML"); + static const auto fieldHTML = clazz->getStaticField("HTML"); + static const auto fieldPLAINTEXT = clazz->getStaticField("PLAINTEXT"); switch (value) { - case NitroRenderer::PLAINTEXT: - return clazz->getStaticFieldValue(fieldPLAINTEXT); - case NitroRenderer::HTML: + case Renderer::HTML: return clazz->getStaticFieldValue(fieldHTML); + case Renderer::PLAINTEXT: + return clazz->getStaticFieldValue(fieldPLAINTEXT); default: std::string stringValue = std::to_string(static_cast(value)); throw std::invalid_argument("Invalid enum value (" + stringValue + "!"); diff --git a/nitrogen/generated/android/c++/JRichTextStyle.hpp b/nitrogen/generated/android/c++/JRichTextStyle.hpp deleted file mode 100644 index b95ef09..0000000 --- a/nitrogen/generated/android/c++/JRichTextStyle.hpp +++ /dev/null @@ -1,130 +0,0 @@ -/// -/// JRichTextStyle.hpp -/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. -/// https://github.com/mrousavy/nitro -/// Copyright © 2025 Marc Rousavy @ Margelo -/// - -#pragma once - -#include -#include "RichTextStyle.hpp" - -#include "FontStyle.hpp" -#include "FontWeight.hpp" -#include "JFontStyle.hpp" -#include "JFontWeight.hpp" -#include "JTextAlign.hpp" -#include "JTextDecorationLine.hpp" -#include "JTextDecorationStyle.hpp" -#include "JTextTransform.hpp" -#include "TextAlign.hpp" -#include "TextDecorationLine.hpp" -#include "TextDecorationStyle.hpp" -#include "TextTransform.hpp" -#include -#include - -namespace margelo::nitro::nitrotext { - - using namespace facebook; - - /** - * The C++ JNI bridge between the C++ struct "RichTextStyle" and the the Kotlin data class "RichTextStyle". - */ - struct JRichTextStyle final: public jni::JavaClass { - public: - static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/nitrotext/RichTextStyle;"; - - public: - /** - * Convert this Java/Kotlin-based struct to the C++ struct RichTextStyle by copying all values to C++. - */ - [[maybe_unused]] - [[nodiscard]] - RichTextStyle toCpp() const { - static const auto clazz = javaClassStatic(); - static const auto fieldFontColor = clazz->getField("fontColor"); - jni::local_ref fontColor = this->getFieldValue(fieldFontColor); - static const auto fieldFragmentBackgroundColor = clazz->getField("fragmentBackgroundColor"); - jni::local_ref fragmentBackgroundColor = this->getFieldValue(fieldFragmentBackgroundColor); - static const auto fieldFontSize = clazz->getField("fontSize"); - jni::local_ref fontSize = this->getFieldValue(fieldFontSize); - static const auto fieldFontWeight = clazz->getField("fontWeight"); - jni::local_ref fontWeight = this->getFieldValue(fieldFontWeight); - static const auto fieldFontStyle = clazz->getField("fontStyle"); - jni::local_ref fontStyle = this->getFieldValue(fieldFontStyle); - static const auto fieldFontFamily = clazz->getField("fontFamily"); - jni::local_ref fontFamily = this->getFieldValue(fieldFontFamily); - static const auto fieldLineHeight = clazz->getField("lineHeight"); - jni::local_ref lineHeight = this->getFieldValue(fieldLineHeight); - static const auto fieldLetterSpacing = clazz->getField("letterSpacing"); - jni::local_ref letterSpacing = this->getFieldValue(fieldLetterSpacing); - static const auto fieldTextAlign = clazz->getField("textAlign"); - jni::local_ref textAlign = this->getFieldValue(fieldTextAlign); - static const auto fieldTextTransform = clazz->getField("textTransform"); - jni::local_ref textTransform = this->getFieldValue(fieldTextTransform); - static const auto fieldTextDecorationLine = clazz->getField("textDecorationLine"); - jni::local_ref textDecorationLine = this->getFieldValue(fieldTextDecorationLine); - static const auto fieldTextDecorationColor = clazz->getField("textDecorationColor"); - jni::local_ref textDecorationColor = this->getFieldValue(fieldTextDecorationColor); - static const auto fieldTextDecorationStyle = clazz->getField("textDecorationStyle"); - jni::local_ref textDecorationStyle = this->getFieldValue(fieldTextDecorationStyle); - static const auto fieldMarginTop = clazz->getField("marginTop"); - jni::local_ref marginTop = this->getFieldValue(fieldMarginTop); - static const auto fieldMarginBottom = clazz->getField("marginBottom"); - jni::local_ref marginBottom = this->getFieldValue(fieldMarginBottom); - static const auto fieldMarginLeft = clazz->getField("marginLeft"); - jni::local_ref marginLeft = this->getFieldValue(fieldMarginLeft); - static const auto fieldMarginRight = clazz->getField("marginRight"); - jni::local_ref marginRight = this->getFieldValue(fieldMarginRight); - return RichTextStyle( - fontColor != nullptr ? std::make_optional(fontColor->toStdString()) : std::nullopt, - fragmentBackgroundColor != nullptr ? std::make_optional(fragmentBackgroundColor->toStdString()) : std::nullopt, - fontSize != nullptr ? std::make_optional(fontSize->value()) : std::nullopt, - fontWeight != nullptr ? std::make_optional(fontWeight->toCpp()) : std::nullopt, - fontStyle != nullptr ? std::make_optional(fontStyle->toCpp()) : std::nullopt, - fontFamily != nullptr ? std::make_optional(fontFamily->toStdString()) : std::nullopt, - lineHeight != nullptr ? std::make_optional(lineHeight->value()) : std::nullopt, - letterSpacing != nullptr ? std::make_optional(letterSpacing->value()) : std::nullopt, - textAlign != nullptr ? std::make_optional(textAlign->toCpp()) : std::nullopt, - textTransform != nullptr ? std::make_optional(textTransform->toCpp()) : std::nullopt, - textDecorationLine != nullptr ? std::make_optional(textDecorationLine->toCpp()) : std::nullopt, - textDecorationColor != nullptr ? std::make_optional(textDecorationColor->toStdString()) : std::nullopt, - textDecorationStyle != nullptr ? std::make_optional(textDecorationStyle->toCpp()) : std::nullopt, - marginTop != nullptr ? std::make_optional(marginTop->value()) : std::nullopt, - marginBottom != nullptr ? std::make_optional(marginBottom->value()) : std::nullopt, - marginLeft != nullptr ? std::make_optional(marginLeft->value()) : std::nullopt, - marginRight != nullptr ? std::make_optional(marginRight->value()) : std::nullopt - ); - } - - public: - /** - * Create a Java/Kotlin-based struct by copying all values from the given C++ struct to Java. - */ - [[maybe_unused]] - static jni::local_ref fromCpp(const RichTextStyle& value) { - return newInstance( - value.fontColor.has_value() ? jni::make_jstring(value.fontColor.value()) : nullptr, - value.fragmentBackgroundColor.has_value() ? jni::make_jstring(value.fragmentBackgroundColor.value()) : nullptr, - value.fontSize.has_value() ? jni::JDouble::valueOf(value.fontSize.value()) : nullptr, - value.fontWeight.has_value() ? JFontWeight::fromCpp(value.fontWeight.value()) : nullptr, - value.fontStyle.has_value() ? JFontStyle::fromCpp(value.fontStyle.value()) : nullptr, - value.fontFamily.has_value() ? jni::make_jstring(value.fontFamily.value()) : nullptr, - value.lineHeight.has_value() ? jni::JDouble::valueOf(value.lineHeight.value()) : nullptr, - value.letterSpacing.has_value() ? jni::JDouble::valueOf(value.letterSpacing.value()) : nullptr, - value.textAlign.has_value() ? JTextAlign::fromCpp(value.textAlign.value()) : nullptr, - value.textTransform.has_value() ? JTextTransform::fromCpp(value.textTransform.value()) : nullptr, - value.textDecorationLine.has_value() ? JTextDecorationLine::fromCpp(value.textDecorationLine.value()) : nullptr, - value.textDecorationColor.has_value() ? jni::make_jstring(value.textDecorationColor.value()) : nullptr, - value.textDecorationStyle.has_value() ? JTextDecorationStyle::fromCpp(value.textDecorationStyle.value()) : nullptr, - value.marginTop.has_value() ? jni::JDouble::valueOf(value.marginTop.value()) : nullptr, - value.marginBottom.has_value() ? jni::JDouble::valueOf(value.marginBottom.value()) : nullptr, - value.marginLeft.has_value() ? jni::JDouble::valueOf(value.marginLeft.value()) : nullptr, - value.marginRight.has_value() ? jni::JDouble::valueOf(value.marginRight.value()) : nullptr - ); - } - }; - -} // namespace margelo::nitro::nitrotext diff --git a/nitrogen/generated/android/c++/JRichTextStyleRule.hpp b/nitrogen/generated/android/c++/JRichTextStyleRule.hpp deleted file mode 100644 index 4cc9d71..0000000 --- a/nitrogen/generated/android/c++/JRichTextStyleRule.hpp +++ /dev/null @@ -1,72 +0,0 @@ -/// -/// JRichTextStyleRule.hpp -/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. -/// https://github.com/mrousavy/nitro -/// Copyright © 2025 Marc Rousavy @ Margelo -/// - -#pragma once - -#include -#include "RichTextStyleRule.hpp" - -#include "FontStyle.hpp" -#include "FontWeight.hpp" -#include "JFontStyle.hpp" -#include "JFontWeight.hpp" -#include "JRichTextStyle.hpp" -#include "JTextAlign.hpp" -#include "JTextDecorationLine.hpp" -#include "JTextDecorationStyle.hpp" -#include "JTextTransform.hpp" -#include "RichTextStyle.hpp" -#include "TextAlign.hpp" -#include "TextDecorationLine.hpp" -#include "TextDecorationStyle.hpp" -#include "TextTransform.hpp" -#include -#include - -namespace margelo::nitro::nitrotext { - - using namespace facebook; - - /** - * The C++ JNI bridge between the C++ struct "RichTextStyleRule" and the the Kotlin data class "RichTextStyleRule". - */ - struct JRichTextStyleRule final: public jni::JavaClass { - public: - static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/nitrotext/RichTextStyleRule;"; - - public: - /** - * Convert this Java/Kotlin-based struct to the C++ struct RichTextStyleRule by copying all values to C++. - */ - [[maybe_unused]] - [[nodiscard]] - RichTextStyleRule toCpp() const { - static const auto clazz = javaClassStatic(); - static const auto fieldSelector = clazz->getField("selector"); - jni::local_ref selector = this->getFieldValue(fieldSelector); - static const auto fieldStyle = clazz->getField("style"); - jni::local_ref style = this->getFieldValue(fieldStyle); - return RichTextStyleRule( - selector->toStdString(), - style->toCpp() - ); - } - - public: - /** - * Create a Java/Kotlin-based struct by copying all values from the given C++ struct to Java. - */ - [[maybe_unused]] - static jni::local_ref fromCpp(const RichTextStyleRule& value) { - return newInstance( - jni::make_jstring(value.selector), - JRichTextStyle::fromCpp(value.style) - ); - } - }; - -} // namespace margelo::nitro::nitrotext diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/NitroRenderer.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/Renderer.kt similarity index 64% rename from nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/NitroRenderer.kt rename to nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/Renderer.kt index 89bf445..e2bbe2d 100644 --- a/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/NitroRenderer.kt +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/Renderer.kt @@ -1,5 +1,5 @@ /// -/// NitroRenderer.kt +/// Renderer.kt /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. /// https://github.com/mrousavy/nitro /// Copyright © 2025 Marc Rousavy @ Margelo @@ -11,11 +11,11 @@ import androidx.annotation.Keep import com.facebook.proguard.annotations.DoNotStrip /** - * Represents the JavaScript enum/union "NitroRenderer". + * Represents the JavaScript enum/union "Renderer". */ @DoNotStrip @Keep -enum class NitroRenderer(@DoNotStrip @Keep val value: Int) { - PLAINTEXT(0), - HTML(1); +enum class Renderer(@DoNotStrip @Keep val value: Int) { + HTML(0), + PLAINTEXT(1); } diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/RichTextStyle.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/RichTextStyle.kt deleted file mode 100644 index b548d2f..0000000 --- a/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/RichTextStyle.kt +++ /dev/null @@ -1,77 +0,0 @@ -/// -/// RichTextStyle.kt -/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. -/// https://github.com/mrousavy/nitro -/// Copyright © 2025 Marc Rousavy @ Margelo -/// - -package com.margelo.nitro.nitrotext - -import androidx.annotation.Keep -import com.facebook.proguard.annotations.DoNotStrip -import com.margelo.nitro.core.* - - -/** - * Represents the JavaScript object/struct "RichTextStyle". - */ -@DoNotStrip -@Keep -data class RichTextStyle - @DoNotStrip - @Keep - constructor( - @DoNotStrip - @Keep - val fontColor: String?, - @DoNotStrip - @Keep - val fragmentBackgroundColor: String?, - @DoNotStrip - @Keep - val fontSize: Double?, - @DoNotStrip - @Keep - val fontWeight: FontWeight?, - @DoNotStrip - @Keep - val fontStyle: FontStyle?, - @DoNotStrip - @Keep - val fontFamily: String?, - @DoNotStrip - @Keep - val lineHeight: Double?, - @DoNotStrip - @Keep - val letterSpacing: Double?, - @DoNotStrip - @Keep - val textAlign: TextAlign?, - @DoNotStrip - @Keep - val textTransform: TextTransform?, - @DoNotStrip - @Keep - val textDecorationLine: TextDecorationLine?, - @DoNotStrip - @Keep - val textDecorationColor: String?, - @DoNotStrip - @Keep - val textDecorationStyle: TextDecorationStyle?, - @DoNotStrip - @Keep - val marginTop: Double?, - @DoNotStrip - @Keep - val marginBottom: Double?, - @DoNotStrip - @Keep - val marginLeft: Double?, - @DoNotStrip - @Keep - val marginRight: Double? - ) { - /* main constructor */ -} diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/RichTextStyleRule.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/RichTextStyleRule.kt deleted file mode 100644 index b4d5c32..0000000 --- a/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/RichTextStyleRule.kt +++ /dev/null @@ -1,32 +0,0 @@ -/// -/// RichTextStyleRule.kt -/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. -/// https://github.com/mrousavy/nitro -/// Copyright © 2025 Marc Rousavy @ Margelo -/// - -package com.margelo.nitro.nitrotext - -import androidx.annotation.Keep -import com.facebook.proguard.annotations.DoNotStrip -import com.margelo.nitro.core.* - - -/** - * Represents the JavaScript object/struct "RichTextStyleRule". - */ -@DoNotStrip -@Keep -data class RichTextStyleRule - @DoNotStrip - @Keep - constructor( - @DoNotStrip - @Keep - val selector: String, - @DoNotStrip - @Keep - val style: RichTextStyle - ) { - /* main constructor */ -} diff --git a/nitrogen/generated/ios/swift/NitroRenderer.swift b/nitrogen/generated/ios/swift/NitroRenderer.swift deleted file mode 100644 index 4028603..0000000 --- a/nitrogen/generated/ios/swift/NitroRenderer.swift +++ /dev/null @@ -1,40 +0,0 @@ -/// -/// NitroRenderer.swift -/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. -/// https://github.com/mrousavy/nitro -/// Copyright © 2025 Marc Rousavy @ Margelo -/// - -/** - * Represents the JS union `NitroRenderer`, backed by a C++ enum. - */ -public typealias NitroRenderer = margelo.nitro.nitrotext.NitroRenderer - -public extension NitroRenderer { - /** - * Get a NitroRenderer for the given String value, or - * return `nil` if the given value was invalid/unknown. - */ - init?(fromString string: String) { - switch string { - case "plaintext": - self = .plaintext - case "html": - self = .html - default: - return nil - } - } - - /** - * Get the String value this NitroRenderer represents. - */ - var stringValue: String { - switch self { - case .plaintext: - return "plaintext" - case .html: - return "html" - } - } -} diff --git a/nitrogen/generated/ios/swift/RichTextStyle.swift b/nitrogen/generated/ios/swift/RichTextStyle.swift deleted file mode 100644 index ce4ce46..0000000 --- a/nitrogen/generated/ios/swift/RichTextStyle.swift +++ /dev/null @@ -1,443 +0,0 @@ -/// -/// RichTextStyle.swift -/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. -/// https://github.com/mrousavy/nitro -/// Copyright © 2025 Marc Rousavy @ Margelo -/// - -import NitroModules - -/** - * Represents an instance of `RichTextStyle`, backed by a C++ struct. - */ -public typealias RichTextStyle = margelo.nitro.nitrotext.RichTextStyle - -public extension RichTextStyle { - private typealias bridge = margelo.nitro.nitrotext.bridge.swift - - /** - * Create a new instance of `RichTextStyle`. - */ - init(fontColor: String?, fragmentBackgroundColor: String?, fontSize: Double?, fontWeight: FontWeight?, fontStyle: FontStyle?, fontFamily: String?, lineHeight: Double?, letterSpacing: Double?, textAlign: TextAlign?, textTransform: TextTransform?, textDecorationLine: TextDecorationLine?, textDecorationColor: String?, textDecorationStyle: TextDecorationStyle?, marginTop: Double?, marginBottom: Double?, marginLeft: Double?, marginRight: Double?) { - self.init({ () -> bridge.std__optional_std__string_ in - if let __unwrappedValue = fontColor { - return bridge.create_std__optional_std__string_(std.string(__unwrappedValue)) - } else { - return .init() - } - }(), { () -> bridge.std__optional_std__string_ in - if let __unwrappedValue = fragmentBackgroundColor { - return bridge.create_std__optional_std__string_(std.string(__unwrappedValue)) - } else { - return .init() - } - }(), { () -> bridge.std__optional_double_ in - if let __unwrappedValue = fontSize { - return bridge.create_std__optional_double_(__unwrappedValue) - } else { - return .init() - } - }(), { () -> bridge.std__optional_FontWeight_ in - if let __unwrappedValue = fontWeight { - return bridge.create_std__optional_FontWeight_(__unwrappedValue) - } else { - return .init() - } - }(), { () -> bridge.std__optional_FontStyle_ in - if let __unwrappedValue = fontStyle { - return bridge.create_std__optional_FontStyle_(__unwrappedValue) - } else { - return .init() - } - }(), { () -> bridge.std__optional_std__string_ in - if let __unwrappedValue = fontFamily { - return bridge.create_std__optional_std__string_(std.string(__unwrappedValue)) - } else { - return .init() - } - }(), { () -> bridge.std__optional_double_ in - if let __unwrappedValue = lineHeight { - return bridge.create_std__optional_double_(__unwrappedValue) - } else { - return .init() - } - }(), { () -> bridge.std__optional_double_ in - if let __unwrappedValue = letterSpacing { - return bridge.create_std__optional_double_(__unwrappedValue) - } else { - return .init() - } - }(), { () -> bridge.std__optional_TextAlign_ in - if let __unwrappedValue = textAlign { - return bridge.create_std__optional_TextAlign_(__unwrappedValue) - } else { - return .init() - } - }(), { () -> bridge.std__optional_TextTransform_ in - if let __unwrappedValue = textTransform { - return bridge.create_std__optional_TextTransform_(__unwrappedValue) - } else { - return .init() - } - }(), { () -> bridge.std__optional_TextDecorationLine_ in - if let __unwrappedValue = textDecorationLine { - return bridge.create_std__optional_TextDecorationLine_(__unwrappedValue) - } else { - return .init() - } - }(), { () -> bridge.std__optional_std__string_ in - if let __unwrappedValue = textDecorationColor { - return bridge.create_std__optional_std__string_(std.string(__unwrappedValue)) - } else { - return .init() - } - }(), { () -> bridge.std__optional_TextDecorationStyle_ in - if let __unwrappedValue = textDecorationStyle { - return bridge.create_std__optional_TextDecorationStyle_(__unwrappedValue) - } else { - return .init() - } - }(), { () -> bridge.std__optional_double_ in - if let __unwrappedValue = marginTop { - return bridge.create_std__optional_double_(__unwrappedValue) - } else { - return .init() - } - }(), { () -> bridge.std__optional_double_ in - if let __unwrappedValue = marginBottom { - return bridge.create_std__optional_double_(__unwrappedValue) - } else { - return .init() - } - }(), { () -> bridge.std__optional_double_ in - if let __unwrappedValue = marginLeft { - return bridge.create_std__optional_double_(__unwrappedValue) - } else { - return .init() - } - }(), { () -> bridge.std__optional_double_ in - if let __unwrappedValue = marginRight { - return bridge.create_std__optional_double_(__unwrappedValue) - } else { - return .init() - } - }()) - } - - var fontColor: String? { - @inline(__always) - get { - return { () -> String? in - if bridge.has_value_std__optional_std__string_(self.__fontColor) { - let __unwrapped = bridge.get_std__optional_std__string_(self.__fontColor) - return String(__unwrapped) - } else { - return nil - } - }() - } - @inline(__always) - set { - self.__fontColor = { () -> bridge.std__optional_std__string_ in - if let __unwrappedValue = newValue { - return bridge.create_std__optional_std__string_(std.string(__unwrappedValue)) - } else { - return .init() - } - }() - } - } - - var fragmentBackgroundColor: String? { - @inline(__always) - get { - return { () -> String? in - if bridge.has_value_std__optional_std__string_(self.__fragmentBackgroundColor) { - let __unwrapped = bridge.get_std__optional_std__string_(self.__fragmentBackgroundColor) - return String(__unwrapped) - } else { - return nil - } - }() - } - @inline(__always) - set { - self.__fragmentBackgroundColor = { () -> bridge.std__optional_std__string_ in - if let __unwrappedValue = newValue { - return bridge.create_std__optional_std__string_(std.string(__unwrappedValue)) - } else { - return .init() - } - }() - } - } - - var fontSize: Double? { - @inline(__always) - get { - return self.__fontSize.value - } - @inline(__always) - set { - self.__fontSize = { () -> bridge.std__optional_double_ in - if let __unwrappedValue = newValue { - return bridge.create_std__optional_double_(__unwrappedValue) - } else { - return .init() - } - }() - } - } - - var fontWeight: FontWeight? { - @inline(__always) - get { - return self.__fontWeight.value - } - @inline(__always) - set { - self.__fontWeight = { () -> bridge.std__optional_FontWeight_ in - if let __unwrappedValue = newValue { - return bridge.create_std__optional_FontWeight_(__unwrappedValue) - } else { - return .init() - } - }() - } - } - - var fontStyle: FontStyle? { - @inline(__always) - get { - return self.__fontStyle.value - } - @inline(__always) - set { - self.__fontStyle = { () -> bridge.std__optional_FontStyle_ in - if let __unwrappedValue = newValue { - return bridge.create_std__optional_FontStyle_(__unwrappedValue) - } else { - return .init() - } - }() - } - } - - var fontFamily: String? { - @inline(__always) - get { - return { () -> String? in - if bridge.has_value_std__optional_std__string_(self.__fontFamily) { - let __unwrapped = bridge.get_std__optional_std__string_(self.__fontFamily) - return String(__unwrapped) - } else { - return nil - } - }() - } - @inline(__always) - set { - self.__fontFamily = { () -> bridge.std__optional_std__string_ in - if let __unwrappedValue = newValue { - return bridge.create_std__optional_std__string_(std.string(__unwrappedValue)) - } else { - return .init() - } - }() - } - } - - var lineHeight: Double? { - @inline(__always) - get { - return self.__lineHeight.value - } - @inline(__always) - set { - self.__lineHeight = { () -> bridge.std__optional_double_ in - if let __unwrappedValue = newValue { - return bridge.create_std__optional_double_(__unwrappedValue) - } else { - return .init() - } - }() - } - } - - var letterSpacing: Double? { - @inline(__always) - get { - return self.__letterSpacing.value - } - @inline(__always) - set { - self.__letterSpacing = { () -> bridge.std__optional_double_ in - if let __unwrappedValue = newValue { - return bridge.create_std__optional_double_(__unwrappedValue) - } else { - return .init() - } - }() - } - } - - var textAlign: TextAlign? { - @inline(__always) - get { - return self.__textAlign.value - } - @inline(__always) - set { - self.__textAlign = { () -> bridge.std__optional_TextAlign_ in - if let __unwrappedValue = newValue { - return bridge.create_std__optional_TextAlign_(__unwrappedValue) - } else { - return .init() - } - }() - } - } - - var textTransform: TextTransform? { - @inline(__always) - get { - return self.__textTransform.value - } - @inline(__always) - set { - self.__textTransform = { () -> bridge.std__optional_TextTransform_ in - if let __unwrappedValue = newValue { - return bridge.create_std__optional_TextTransform_(__unwrappedValue) - } else { - return .init() - } - }() - } - } - - var textDecorationLine: TextDecorationLine? { - @inline(__always) - get { - return self.__textDecorationLine.value - } - @inline(__always) - set { - self.__textDecorationLine = { () -> bridge.std__optional_TextDecorationLine_ in - if let __unwrappedValue = newValue { - return bridge.create_std__optional_TextDecorationLine_(__unwrappedValue) - } else { - return .init() - } - }() - } - } - - var textDecorationColor: String? { - @inline(__always) - get { - return { () -> String? in - if bridge.has_value_std__optional_std__string_(self.__textDecorationColor) { - let __unwrapped = bridge.get_std__optional_std__string_(self.__textDecorationColor) - return String(__unwrapped) - } else { - return nil - } - }() - } - @inline(__always) - set { - self.__textDecorationColor = { () -> bridge.std__optional_std__string_ in - if let __unwrappedValue = newValue { - return bridge.create_std__optional_std__string_(std.string(__unwrappedValue)) - } else { - return .init() - } - }() - } - } - - var textDecorationStyle: TextDecorationStyle? { - @inline(__always) - get { - return self.__textDecorationStyle.value - } - @inline(__always) - set { - self.__textDecorationStyle = { () -> bridge.std__optional_TextDecorationStyle_ in - if let __unwrappedValue = newValue { - return bridge.create_std__optional_TextDecorationStyle_(__unwrappedValue) - } else { - return .init() - } - }() - } - } - - var marginTop: Double? { - @inline(__always) - get { - return self.__marginTop.value - } - @inline(__always) - set { - self.__marginTop = { () -> bridge.std__optional_double_ in - if let __unwrappedValue = newValue { - return bridge.create_std__optional_double_(__unwrappedValue) - } else { - return .init() - } - }() - } - } - - var marginBottom: Double? { - @inline(__always) - get { - return self.__marginBottom.value - } - @inline(__always) - set { - self.__marginBottom = { () -> bridge.std__optional_double_ in - if let __unwrappedValue = newValue { - return bridge.create_std__optional_double_(__unwrappedValue) - } else { - return .init() - } - }() - } - } - - var marginLeft: Double? { - @inline(__always) - get { - return self.__marginLeft.value - } - @inline(__always) - set { - self.__marginLeft = { () -> bridge.std__optional_double_ in - if let __unwrappedValue = newValue { - return bridge.create_std__optional_double_(__unwrappedValue) - } else { - return .init() - } - }() - } - } - - var marginRight: Double? { - @inline(__always) - get { - return self.__marginRight.value - } - @inline(__always) - set { - self.__marginRight = { () -> bridge.std__optional_double_ in - if let __unwrappedValue = newValue { - return bridge.create_std__optional_double_(__unwrappedValue) - } else { - return .init() - } - }() - } - } -} diff --git a/nitrogen/generated/ios/swift/RichTextStyleRule.swift b/nitrogen/generated/ios/swift/RichTextStyleRule.swift deleted file mode 100644 index 8c708e4..0000000 --- a/nitrogen/generated/ios/swift/RichTextStyleRule.swift +++ /dev/null @@ -1,46 +0,0 @@ -/// -/// RichTextStyleRule.swift -/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. -/// https://github.com/mrousavy/nitro -/// Copyright © 2025 Marc Rousavy @ Margelo -/// - -import NitroModules - -/** - * Represents an instance of `RichTextStyleRule`, backed by a C++ struct. - */ -public typealias RichTextStyleRule = margelo.nitro.nitrotext.RichTextStyleRule - -public extension RichTextStyleRule { - private typealias bridge = margelo.nitro.nitrotext.bridge.swift - - /** - * Create a new instance of `RichTextStyleRule`. - */ - init(selector: String, style: RichTextStyle) { - self.init(std.string(selector), style) - } - - var selector: String { - @inline(__always) - get { - return String(self.__selector) - } - @inline(__always) - set { - self.__selector = std.string(newValue) - } - } - - var style: RichTextStyle { - @inline(__always) - get { - return self.__style - } - @inline(__always) - set { - self.__style = newValue - } - } -} diff --git a/nitrogen/generated/shared/c++/NitroRenderer.hpp b/nitrogen/generated/shared/c++/NitroRenderer.hpp deleted file mode 100644 index 49305e5..0000000 --- a/nitrogen/generated/shared/c++/NitroRenderer.hpp +++ /dev/null @@ -1,76 +0,0 @@ -/// -/// NitroRenderer.hpp -/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. -/// https://github.com/mrousavy/nitro -/// Copyright © 2025 Marc Rousavy @ Margelo -/// - -#pragma once - -#if __has_include() -#include -#else -#error NitroModules cannot be found! Are you sure you installed NitroModules properly? -#endif -#if __has_include() -#include -#else -#error NitroModules cannot be found! Are you sure you installed NitroModules properly? -#endif -#if __has_include() -#include -#else -#error NitroModules cannot be found! Are you sure you installed NitroModules properly? -#endif - -namespace margelo::nitro::nitrotext { - - /** - * An enum which can be represented as a JavaScript union (NitroRenderer). - */ - enum class NitroRenderer { - PLAINTEXT SWIFT_NAME(plaintext) = 0, - HTML SWIFT_NAME(html) = 1, - } CLOSED_ENUM; - -} // namespace margelo::nitro::nitrotext - -namespace margelo::nitro { - - // C++ NitroRenderer <> JS NitroRenderer (union) - template <> - struct JSIConverter final { - static inline margelo::nitro::nitrotext::NitroRenderer fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) { - std::string unionValue = JSIConverter::fromJSI(runtime, arg); - switch (hashString(unionValue.c_str(), unionValue.size())) { - case hashString("plaintext"): return margelo::nitro::nitrotext::NitroRenderer::PLAINTEXT; - case hashString("html"): return margelo::nitro::nitrotext::NitroRenderer::HTML; - default: [[unlikely]] - throw std::invalid_argument("Cannot convert \"" + unionValue + "\" to enum NitroRenderer - invalid value!"); - } - } - static inline jsi::Value toJSI(jsi::Runtime& runtime, margelo::nitro::nitrotext::NitroRenderer arg) { - switch (arg) { - case margelo::nitro::nitrotext::NitroRenderer::PLAINTEXT: return JSIConverter::toJSI(runtime, "plaintext"); - case margelo::nitro::nitrotext::NitroRenderer::HTML: return JSIConverter::toJSI(runtime, "html"); - default: [[unlikely]] - throw std::invalid_argument("Cannot convert NitroRenderer to JS - invalid value: " - + std::to_string(static_cast(arg)) + "!"); - } - } - static inline bool canConvert(jsi::Runtime& runtime, const jsi::Value& value) { - if (!value.isString()) { - return false; - } - std::string unionValue = JSIConverter::fromJSI(runtime, value); - switch (hashString(unionValue.c_str(), unionValue.size())) { - case hashString("plaintext"): - case hashString("html"): - return true; - default: - return false; - } - } - }; - -} // namespace margelo::nitro diff --git a/nitrogen/generated/shared/c++/RichTextStyle.hpp b/nitrogen/generated/shared/c++/RichTextStyle.hpp deleted file mode 100644 index 7933df8..0000000 --- a/nitrogen/generated/shared/c++/RichTextStyle.hpp +++ /dev/null @@ -1,149 +0,0 @@ -/// -/// RichTextStyle.hpp -/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. -/// https://github.com/mrousavy/nitro -/// Copyright © 2025 Marc Rousavy @ Margelo -/// - -#pragma once - -#if __has_include() -#include -#else -#error NitroModules cannot be found! Are you sure you installed NitroModules properly? -#endif -#if __has_include() -#include -#else -#error NitroModules cannot be found! Are you sure you installed NitroModules properly? -#endif - -// Forward declaration of `FontWeight` to properly resolve imports. -namespace margelo::nitro::nitrotext { enum class FontWeight; } -// Forward declaration of `FontStyle` to properly resolve imports. -namespace margelo::nitro::nitrotext { enum class FontStyle; } -// Forward declaration of `TextAlign` to properly resolve imports. -namespace margelo::nitro::nitrotext { enum class TextAlign; } -// Forward declaration of `TextTransform` to properly resolve imports. -namespace margelo::nitro::nitrotext { enum class TextTransform; } -// Forward declaration of `TextDecorationLine` to properly resolve imports. -namespace margelo::nitro::nitrotext { enum class TextDecorationLine; } -// Forward declaration of `TextDecorationStyle` to properly resolve imports. -namespace margelo::nitro::nitrotext { enum class TextDecorationStyle; } - -#include -#include -#include "FontWeight.hpp" -#include "FontStyle.hpp" -#include "TextAlign.hpp" -#include "TextTransform.hpp" -#include "TextDecorationLine.hpp" -#include "TextDecorationStyle.hpp" - -namespace margelo::nitro::nitrotext { - - /** - * A struct which can be represented as a JavaScript object (RichTextStyle). - */ - struct RichTextStyle { - public: - std::optional fontColor SWIFT_PRIVATE; - std::optional fragmentBackgroundColor SWIFT_PRIVATE; - std::optional fontSize SWIFT_PRIVATE; - std::optional fontWeight SWIFT_PRIVATE; - std::optional fontStyle SWIFT_PRIVATE; - std::optional fontFamily SWIFT_PRIVATE; - std::optional lineHeight SWIFT_PRIVATE; - std::optional letterSpacing SWIFT_PRIVATE; - std::optional textAlign SWIFT_PRIVATE; - std::optional textTransform SWIFT_PRIVATE; - std::optional textDecorationLine SWIFT_PRIVATE; - std::optional textDecorationColor SWIFT_PRIVATE; - std::optional textDecorationStyle SWIFT_PRIVATE; - std::optional marginTop SWIFT_PRIVATE; - std::optional marginBottom SWIFT_PRIVATE; - std::optional marginLeft SWIFT_PRIVATE; - std::optional marginRight SWIFT_PRIVATE; - - public: - RichTextStyle() = default; - explicit RichTextStyle(std::optional fontColor, std::optional fragmentBackgroundColor, std::optional fontSize, std::optional fontWeight, std::optional fontStyle, std::optional fontFamily, std::optional lineHeight, std::optional letterSpacing, std::optional textAlign, std::optional textTransform, std::optional textDecorationLine, std::optional textDecorationColor, std::optional textDecorationStyle, std::optional marginTop, std::optional marginBottom, std::optional marginLeft, std::optional marginRight): fontColor(fontColor), fragmentBackgroundColor(fragmentBackgroundColor), fontSize(fontSize), fontWeight(fontWeight), fontStyle(fontStyle), fontFamily(fontFamily), lineHeight(lineHeight), letterSpacing(letterSpacing), textAlign(textAlign), textTransform(textTransform), textDecorationLine(textDecorationLine), textDecorationColor(textDecorationColor), textDecorationStyle(textDecorationStyle), marginTop(marginTop), marginBottom(marginBottom), marginLeft(marginLeft), marginRight(marginRight) {} - }; - -} // namespace margelo::nitro::nitrotext - -namespace margelo::nitro { - - // C++ RichTextStyle <> JS RichTextStyle (object) - template <> - struct JSIConverter final { - static inline margelo::nitro::nitrotext::RichTextStyle fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) { - jsi::Object obj = arg.asObject(runtime); - return margelo::nitro::nitrotext::RichTextStyle( - JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, "fontColor")), - JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, "fragmentBackgroundColor")), - JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, "fontSize")), - JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, "fontWeight")), - JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, "fontStyle")), - JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, "fontFamily")), - JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, "lineHeight")), - JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, "letterSpacing")), - JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, "textAlign")), - JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, "textTransform")), - JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, "textDecorationLine")), - JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, "textDecorationColor")), - JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, "textDecorationStyle")), - JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, "marginTop")), - JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, "marginBottom")), - JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, "marginLeft")), - JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, "marginRight")) - ); - } - static inline jsi::Value toJSI(jsi::Runtime& runtime, const margelo::nitro::nitrotext::RichTextStyle& arg) { - jsi::Object obj(runtime); - obj.setProperty(runtime, "fontColor", JSIConverter>::toJSI(runtime, arg.fontColor)); - obj.setProperty(runtime, "fragmentBackgroundColor", JSIConverter>::toJSI(runtime, arg.fragmentBackgroundColor)); - obj.setProperty(runtime, "fontSize", JSIConverter>::toJSI(runtime, arg.fontSize)); - obj.setProperty(runtime, "fontWeight", JSIConverter>::toJSI(runtime, arg.fontWeight)); - obj.setProperty(runtime, "fontStyle", JSIConverter>::toJSI(runtime, arg.fontStyle)); - obj.setProperty(runtime, "fontFamily", JSIConverter>::toJSI(runtime, arg.fontFamily)); - obj.setProperty(runtime, "lineHeight", JSIConverter>::toJSI(runtime, arg.lineHeight)); - obj.setProperty(runtime, "letterSpacing", JSIConverter>::toJSI(runtime, arg.letterSpacing)); - obj.setProperty(runtime, "textAlign", JSIConverter>::toJSI(runtime, arg.textAlign)); - obj.setProperty(runtime, "textTransform", JSIConverter>::toJSI(runtime, arg.textTransform)); - obj.setProperty(runtime, "textDecorationLine", JSIConverter>::toJSI(runtime, arg.textDecorationLine)); - obj.setProperty(runtime, "textDecorationColor", JSIConverter>::toJSI(runtime, arg.textDecorationColor)); - obj.setProperty(runtime, "textDecorationStyle", JSIConverter>::toJSI(runtime, arg.textDecorationStyle)); - obj.setProperty(runtime, "marginTop", JSIConverter>::toJSI(runtime, arg.marginTop)); - obj.setProperty(runtime, "marginBottom", JSIConverter>::toJSI(runtime, arg.marginBottom)); - obj.setProperty(runtime, "marginLeft", JSIConverter>::toJSI(runtime, arg.marginLeft)); - obj.setProperty(runtime, "marginRight", JSIConverter>::toJSI(runtime, arg.marginRight)); - return obj; - } - static inline bool canConvert(jsi::Runtime& runtime, const jsi::Value& value) { - if (!value.isObject()) { - return false; - } - jsi::Object obj = value.getObject(runtime); - if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, "fontColor"))) return false; - if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, "fragmentBackgroundColor"))) return false; - if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, "fontSize"))) return false; - if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, "fontWeight"))) return false; - if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, "fontStyle"))) return false; - if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, "fontFamily"))) return false; - if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, "lineHeight"))) return false; - if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, "letterSpacing"))) return false; - if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, "textAlign"))) return false; - if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, "textTransform"))) return false; - if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, "textDecorationLine"))) return false; - if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, "textDecorationColor"))) return false; - if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, "textDecorationStyle"))) return false; - if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, "marginTop"))) return false; - if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, "marginBottom"))) return false; - if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, "marginLeft"))) return false; - if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, "marginRight"))) return false; - return true; - } - }; - -} // namespace margelo::nitro diff --git a/nitrogen/generated/shared/c++/RichTextStyleRule.hpp b/nitrogen/generated/shared/c++/RichTextStyleRule.hpp deleted file mode 100644 index 7d5963c..0000000 --- a/nitrogen/generated/shared/c++/RichTextStyleRule.hpp +++ /dev/null @@ -1,73 +0,0 @@ -/// -/// RichTextStyleRule.hpp -/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. -/// https://github.com/mrousavy/nitro -/// Copyright © 2025 Marc Rousavy @ Margelo -/// - -#pragma once - -#if __has_include() -#include -#else -#error NitroModules cannot be found! Are you sure you installed NitroModules properly? -#endif -#if __has_include() -#include -#else -#error NitroModules cannot be found! Are you sure you installed NitroModules properly? -#endif - -// Forward declaration of `RichTextStyle` to properly resolve imports. -namespace margelo::nitro::nitrotext { struct RichTextStyle; } - -#include -#include "RichTextStyle.hpp" - -namespace margelo::nitro::nitrotext { - - /** - * A struct which can be represented as a JavaScript object (RichTextStyleRule). - */ - struct RichTextStyleRule { - public: - std::string selector SWIFT_PRIVATE; - RichTextStyle style SWIFT_PRIVATE; - - public: - RichTextStyleRule() = default; - explicit RichTextStyleRule(std::string selector, RichTextStyle style): selector(selector), style(style) {} - }; - -} // namespace margelo::nitro::nitrotext - -namespace margelo::nitro { - - // C++ RichTextStyleRule <> JS RichTextStyleRule (object) - template <> - struct JSIConverter final { - static inline margelo::nitro::nitrotext::RichTextStyleRule fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) { - jsi::Object obj = arg.asObject(runtime); - return margelo::nitro::nitrotext::RichTextStyleRule( - JSIConverter::fromJSI(runtime, obj.getProperty(runtime, "selector")), - JSIConverter::fromJSI(runtime, obj.getProperty(runtime, "style")) - ); - } - static inline jsi::Value toJSI(jsi::Runtime& runtime, const margelo::nitro::nitrotext::RichTextStyleRule& arg) { - jsi::Object obj(runtime); - obj.setProperty(runtime, "selector", JSIConverter::toJSI(runtime, arg.selector)); - obj.setProperty(runtime, "style", JSIConverter::toJSI(runtime, arg.style)); - return obj; - } - static inline bool canConvert(jsi::Runtime& runtime, const jsi::Value& value) { - if (!value.isObject()) { - return false; - } - jsi::Object obj = value.getObject(runtime); - if (!JSIConverter::canConvert(runtime, obj.getProperty(runtime, "selector"))) return false; - if (!JSIConverter::canConvert(runtime, obj.getProperty(runtime, "style"))) return false; - return true; - } - }; - -} // namespace margelo::nitro From d29f0e20d500852aefa3a7764d71eb18aad113df Mon Sep 17 00:00:00 2001 From: Patrick Kabwe Date: Sun, 23 Nov 2025 21:32:52 +0200 Subject: [PATCH 08/12] chore: remove unused span files - Remove BulletListSpan, NumberedListSpan, VerticalMarginSpan, LetterSpacingSpan - Remove duplicate NitroLineHeightSpan from parent directory - Keep only NitroLineHeightSpan and UrlSpanNoUnderline which are actively used --- .../java/com/nitrotext/NitroLineHeightSpan.kt | 29 ----------- .../com/nitrotext/spans/BulletListSpan.kt | 48 ------------------ .../com/nitrotext/spans/LetterSpacingSpan.kt | 14 ------ .../com/nitrotext/spans/NumberedListSpan.kt | 49 ------------------- .../com/nitrotext/spans/VerticalMarginSpan.kt | 31 ------------ 5 files changed, 171 deletions(-) delete mode 100644 android/src/main/java/com/nitrotext/NitroLineHeightSpan.kt delete mode 100644 android/src/main/java/com/nitrotext/spans/BulletListSpan.kt delete mode 100644 android/src/main/java/com/nitrotext/spans/LetterSpacingSpan.kt delete mode 100644 android/src/main/java/com/nitrotext/spans/NumberedListSpan.kt delete mode 100644 android/src/main/java/com/nitrotext/spans/VerticalMarginSpan.kt diff --git a/android/src/main/java/com/nitrotext/NitroLineHeightSpan.kt b/android/src/main/java/com/nitrotext/NitroLineHeightSpan.kt deleted file mode 100644 index cc1487a..0000000 --- a/android/src/main/java/com/nitrotext/NitroLineHeightSpan.kt +++ /dev/null @@ -1,29 +0,0 @@ -package com.nitrotext - -import android.graphics.Paint -import android.text.style.LineHeightSpan -import kotlin.math.ceil -import kotlin.math.floor - -/** - * Matches React Native's CustomLineHeightSpan so lineHeight behaves the same as . - */ -class NitroLineHeightSpan(heightPx: Float) : LineHeightSpan { - private val lineHeight: Int = ceil(heightPx.toDouble()).toInt() - - override fun chooseHeight( - text: CharSequence, - start: Int, - end: Int, - spanstartv: Int, - v: Int, - fm: Paint.FontMetricsInt - ) { - val leading = lineHeight - ((-fm.ascent) + fm.descent) - val halfLeading = leading / 2f - fm.ascent -= ceil(halfLeading.toDouble()).toInt() - fm.descent += floor(halfLeading.toDouble()).toInt() - if (start == 0) fm.top = fm.ascent - if (end == text.length) fm.bottom = fm.descent - } -} diff --git a/android/src/main/java/com/nitrotext/spans/BulletListSpan.kt b/android/src/main/java/com/nitrotext/spans/BulletListSpan.kt deleted file mode 100644 index 31f236c..0000000 --- a/android/src/main/java/com/nitrotext/spans/BulletListSpan.kt +++ /dev/null @@ -1,48 +0,0 @@ -package com.nitrotext.spans - -import android.graphics.Canvas -import android.graphics.Paint -import android.text.style.LeadingMarginSpan - -internal class BulletListSpan( - private val leadingMargin: Int, - private val gapWidth: Int, - private val radiusPx: Float -) : LeadingMarginSpan.LeadingMarginSpan2 { - - override fun getLeadingMargin(first: Boolean): Int = leadingMargin - - override fun getLeadingMarginLineCount(): Int = 1 - - override fun drawLeadingMargin( - canvas: Canvas, - paint: Paint, - x: Int, - dir: Int, - top: Int, - baseline: Int, - bottom: Int, - text: CharSequence, - start: Int, - end: Int, - first: Boolean, - layout: android.text.Layout? - ) { - if (!first || radiusPx <= 0f) return - val previousStyle = paint.style - val previousColor = paint.color - paint.style = Paint.Style.FILL - - val gap = gapWidth.toFloat() / 2f - val centerX = if (dir > 0) { - x + radiusPx + gap - } else { - x - radiusPx - gap - } - val centerY = (top + bottom) / 2f - canvas.drawCircle(centerX, centerY, radiusPx, paint) - - paint.style = previousStyle - paint.color = previousColor - } -} diff --git a/android/src/main/java/com/nitrotext/spans/LetterSpacingSpan.kt b/android/src/main/java/com/nitrotext/spans/LetterSpacingSpan.kt deleted file mode 100644 index 7d2ea71..0000000 --- a/android/src/main/java/com/nitrotext/spans/LetterSpacingSpan.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.nitrotext.spans - -import android.text.TextPaint -import android.text.style.MetricAffectingSpan - -internal class LetterSpacingSpan(private val em: Float) : MetricAffectingSpan() { - override fun updateDrawState(tp: TextPaint) { - tp.letterSpacing = em - } - - override fun updateMeasureState(tp: TextPaint) { - tp.letterSpacing = em - } -} \ No newline at end of file diff --git a/android/src/main/java/com/nitrotext/spans/NumberedListSpan.kt b/android/src/main/java/com/nitrotext/spans/NumberedListSpan.kt deleted file mode 100644 index f23872f..0000000 --- a/android/src/main/java/com/nitrotext/spans/NumberedListSpan.kt +++ /dev/null @@ -1,49 +0,0 @@ -package com.nitrotext.spans - -import android.graphics.Canvas -import android.graphics.Paint -import android.text.style.LeadingMarginSpan - -internal class NumberedListSpan( - private val number: Int, - private val leadingMargin: Int, - private val gapWidth: Int -) : LeadingMarginSpan.LeadingMarginSpan2 { - - override fun getLeadingMargin(first: Boolean): Int = leadingMargin - - override fun getLeadingMarginLineCount(): Int = 1 - - override fun drawLeadingMargin( - canvas: Canvas, - paint: Paint, - x: Int, - dir: Int, - top: Int, - baseline: Int, - bottom: Int, - text: CharSequence, - start: Int, - end: Int, - first: Boolean, - layout: android.text.Layout? - ) { - if (!first || number <= 0) return - val label = "$number." - val previousStyle = paint.style - paint.style = Paint.Style.FILL - - val textStart = x + dir * leadingMargin - val labelWidth = paint.measureText(label) - val gap = gapWidth.toFloat() - val labelX = if (dir > 0) { - textStart - gap - labelWidth - } else { - textStart + gap - } - - canvas.drawText(label, labelX, baseline.toFloat(), paint) - - paint.style = previousStyle - } -} diff --git a/android/src/main/java/com/nitrotext/spans/VerticalMarginSpan.kt b/android/src/main/java/com/nitrotext/spans/VerticalMarginSpan.kt deleted file mode 100644 index 52a2955..0000000 --- a/android/src/main/java/com/nitrotext/spans/VerticalMarginSpan.kt +++ /dev/null @@ -1,31 +0,0 @@ -package com.nitrotext.spans - -import android.graphics.Paint -import android.text.style.LineHeightSpan -import kotlin.math.roundToInt - -internal class VerticalMarginSpan( - private val top: Float, - private val bottom: Float -) : LineHeightSpan { - override fun chooseHeight( - text: CharSequence?, - start: Int, - end: Int, - spanstartv: Int, - v: Int, - fm: Paint.FontMetricsInt? - ) { - fm ?: return - val addTop = top.roundToInt() - val addBottom = bottom.roundToInt() - if (addTop != 0) { - fm.top -= addTop - fm.ascent -= addTop - } - if (addBottom != 0) { - fm.bottom += addBottom - fm.descent += addBottom - } - } -} From 012592d85934538bf4a56020c212f0e7dda5b3bf Mon Sep 17 00:00:00 2001 From: Patrick Kabwe Date: Sun, 23 Nov 2025 21:34:38 +0200 Subject: [PATCH 09/12] fix: ensure consistent link color application in NitroText - Updated the default link color assignment in NitroTextImpl to maintain consistency with the intended design, ensuring that the system link color is applied correctly when no explicit color is provided. --- android/src/main/java/com/nitrotext/NitroTextImpl.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/src/main/java/com/nitrotext/NitroTextImpl.kt b/android/src/main/java/com/nitrotext/NitroTextImpl.kt index a52f94c..134c115 100644 --- a/android/src/main/java/com/nitrotext/NitroTextImpl.kt +++ b/android/src/main/java/com/nitrotext/NitroTextImpl.kt @@ -227,7 +227,7 @@ class NitroTextImpl(private val view: AppCompatTextView) { // Apply default link color if fragment doesn't have explicit color if (frag.fontColor == null) { // Use system link color (typically blue) - val linkColor = android.graphics.Color.parseColor("#007AFF") // iOS-like blue + val linkColor = android.graphics.Color.parseColor("#007AFF") builder.setSpan(ForegroundColorSpan(linkColor), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) } } From b574401c13ca41e14867e8095c89765f3ff45822 Mon Sep 17 00:00:00 2001 From: Patrick Kabwe Date: Sun, 23 Nov 2025 21:40:30 +0200 Subject: [PATCH 10/12] fix: allow custom colors for links on Android - Modify UrlSpanNoUnderline to not apply default link color - Apply color span after URLSpan to ensure custom colors take precedence - Support both custom link colors and default blue when no color specified --- .../main/java/com/nitrotext/NitroTextImpl.kt | 26 +++++++++++++------ .../com/nitrotext/spans/UrlSpanNoUnderline.kt | 3 ++- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/android/src/main/java/com/nitrotext/NitroTextImpl.kt b/android/src/main/java/com/nitrotext/NitroTextImpl.kt index 134c115..5df800b 100644 --- a/android/src/main/java/com/nitrotext/NitroTextImpl.kt +++ b/android/src/main/java/com/nitrotext/NitroTextImpl.kt @@ -195,9 +195,14 @@ class NitroTextImpl(private val view: AppCompatTextView) { val end = builder.length if (start == end) continue - frag.fontColor?.let { parseColorSafe(it)?.let { c -> - builder.setSpan(ForegroundColorSpan(c), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) - }} + // Apply font color for non-link fragments, or store for links to apply after URLSpan + val hasLink = frag.linkUrl?.isNotEmpty() == true + if (!hasLink) { + frag.fontColor?.let { parseColorSafe(it)?.let { c -> + builder.setSpan(ForegroundColorSpan(c), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + }} + } + frag.fragmentBackgroundColor?.let { parseColorSafe(it)?.let { c -> builder.setSpan(BackgroundColorSpan(c), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) }} @@ -224,11 +229,16 @@ class NitroTextImpl(private val view: AppCompatTextView) { if (url.isNotEmpty()) { builder.setSpan(UrlSpanNoUnderline(url), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) hasLinks = true - // Apply default link color if fragment doesn't have explicit color - if (frag.fontColor == null) { - // Use system link color (typically blue) - val linkColor = android.graphics.Color.parseColor("#007AFF") - builder.setSpan(ForegroundColorSpan(linkColor), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + // Apply link color - use custom color if provided, otherwise use default + // This must be applied AFTER the URLSpan to ensure it takes precedence + val linkColor = if (frag.fontColor != null) { + parseColorSafe(frag.fontColor) + } else { + // Use system link color (typically blue) as default + android.graphics.Color.parseColor("#007AFF") + } + linkColor?.let { c -> + builder.setSpan(ForegroundColorSpan(c), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) } } } diff --git a/android/src/main/java/com/nitrotext/spans/UrlSpanNoUnderline.kt b/android/src/main/java/com/nitrotext/spans/UrlSpanNoUnderline.kt index 8db1e62..e7c49ef 100644 --- a/android/src/main/java/com/nitrotext/spans/UrlSpanNoUnderline.kt +++ b/android/src/main/java/com/nitrotext/spans/UrlSpanNoUnderline.kt @@ -5,7 +5,8 @@ import android.text.style.URLSpan internal class UrlSpanNoUnderline(url: String) : URLSpan(url) { override fun updateDrawState(ds: TextPaint) { - super.updateDrawState(ds) + // Don't call super.updateDrawState() to avoid default link color + // This allows custom colors from ForegroundColorSpan to be applied ds.isUnderlineText = false } } From 0c6b9ea5b4f6e8ec74e7efe4eec7a56f61487854 Mon Sep 17 00:00:00 2001 From: Patrick Kabwe Date: Sun, 23 Nov 2025 21:44:05 +0200 Subject: [PATCH 11/12] feat: enhance NitroTextView layout handling and action mode configuration - Implemented a layout change listener in NitroTextView to ensure proper height adjustment for content expansion. - Renamed createCustomActionModeCallback to configureActionModeCallback for clarity in action mode handling. - Updated NitroText interface to use consistent object notation for platform specifications. --- android/src/main/java/com/nitrotext/NitroTextView.kt | 4 ++-- src/specs/nitro-text.nitro.ts | 2 +- src/utils.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/android/src/main/java/com/nitrotext/NitroTextView.kt b/android/src/main/java/com/nitrotext/NitroTextView.kt index 5300f09..14d477e 100644 --- a/android/src/main/java/com/nitrotext/NitroTextView.kt +++ b/android/src/main/java/com/nitrotext/NitroTextView.kt @@ -54,10 +54,10 @@ class NitroTextView(ctx: Context) : ReactViewGroup(ctx) { private fun updateActionModeCallback() { textView.customSelectionActionModeCallback = customMenus?.takeIf { it.isNotEmpty() } - ?.let { createCustomActionModeCallback(it) } + ?.let { configureActionModeCallback(it) } } - private fun createCustomActionModeCallback(menus: Array): ActionMode.Callback { + private fun configureActionModeCallback(menus: Array): ActionMode.Callback { val menuItemMap = menus.mapIndexedNotNull { index, item -> if (item.title.isNotEmpty()) { Menu.FIRST + index to item } else null }.toMap() diff --git a/src/specs/nitro-text.nitro.ts b/src/specs/nitro-text.nitro.ts index 47f295c..98d4832 100755 --- a/src/specs/nitro-text.nitro.ts +++ b/src/specs/nitro-text.nitro.ts @@ -148,5 +148,5 @@ export interface NitroTextMethods extends HybridViewMethods {} export type NitroText = HybridView< NitroTextProps, NitroTextMethods, - { ios: 'swift'; android: 'kotlin' } + { ios: 'swift', android: 'kotlin' } > diff --git a/src/utils.ts b/src/utils.ts index dd96e61..48d37e2 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -260,4 +260,4 @@ export function getStyleProps(styles: Partial) { } return props -} \ No newline at end of file +} From 2ab0c5b3e5b282403d0b8a84283905402db7db9e Mon Sep 17 00:00:00 2001 From: Patrick Kabwe Date: Tue, 25 Nov 2025 22:53:56 +0200 Subject: [PATCH 12/12] feat: enhance NitroTextImpl with text alignment and decoration color support - Added support for text alignment in NitroTextImpl, allowing fragments to specify alignment (left, right, center, justify). - Updated applyDecorationSpans to accept a decoration color parameter, enabling custom colors for text decorations. - Modified example screens to demonstrate new text alignment and decoration features. --- .../main/java/com/nitrotext/NitroTextImpl.kt | 21 ++++++++++++++++--- example/src/screens/HtmlScreen.tsx | 9 +++++--- example/src/screens/PlainTextScreen.tsx | 2 +- 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/android/src/main/java/com/nitrotext/NitroTextImpl.kt b/android/src/main/java/com/nitrotext/NitroTextImpl.kt index 5df800b..2674e94 100644 --- a/android/src/main/java/com/nitrotext/NitroTextImpl.kt +++ b/android/src/main/java/com/nitrotext/NitroTextImpl.kt @@ -6,13 +6,16 @@ import android.text.Spannable import android.text.SpannableStringBuilder import android.text.TextUtils import android.text.style.AbsoluteSizeSpan +import android.text.style.AlignmentSpan import android.text.style.BackgroundColorSpan import android.text.style.ForegroundColorSpan import android.text.style.StrikethroughSpan import android.text.style.StyleSpan import android.text.style.TypefaceSpan import android.text.style.UnderlineSpan +import android.text.Layout import android.view.Gravity +import android.view.View import androidx.appcompat.widget.AppCompatTextView import com.facebook.react.uimanager.PixelUtil import com.margelo.nitro.nitrotext.* @@ -159,7 +162,7 @@ class NitroTextImpl(private val view: AppCompatTextView) { val spansRequired = textDecorationLine != null || lineHeightPx != null if (spansRequired) { val builder = SpannableStringBuilder(content) - applyDecorationSpans(builder, 0, builder.length, textDecorationLine) + applyDecorationSpans(builder, 0, builder.length, textDecorationLine, textDecorationColor) lineHeightPx?.let { applyLineHeightSpan(builder, 0, builder.length, it) } view.text = builder } else { @@ -219,8 +222,19 @@ class NitroTextImpl(private val view: AppCompatTextView) { frag.fontFamily?.let { builder.setSpan(TypefaceSpan(it), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) } + // Text Alignment (per-fragment) + frag.textAlign?.let { align -> + val alignment = when (align) { + TextAlign.LEFT -> Layout.Alignment.ALIGN_NORMAL + TextAlign.RIGHT -> Layout.Alignment.ALIGN_OPPOSITE + TextAlign.CENTER -> Layout.Alignment.ALIGN_CENTER + TextAlign.JUSTIFY -> Layout.Alignment.ALIGN_CENTER // Justify not directly supported, use center + TextAlign.AUTO -> Layout.Alignment.ALIGN_NORMAL + } + builder.setSpan(AlignmentSpan.Standard(alignment), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + } // Decorations - applyDecorationSpans(builder, start, end, frag.textDecorationLine) + applyDecorationSpans(builder, start, end, frag.textDecorationLine, frag.textDecorationColor) frag.lineHeight?.let { lh -> resolveLineHeight(lh)?.let { applyLineHeightSpan(builder, start, end, it) } } @@ -261,7 +275,8 @@ class NitroTextImpl(private val view: AppCompatTextView) { view.setTextIsSelectable(isSelectable) } - private fun applyDecorationSpans(builder: SpannableStringBuilder, start: Int, end: Int, line: TextDecorationLine?) { + private fun applyDecorationSpans(builder: SpannableStringBuilder, start: Int, end: Int, line: TextDecorationLine?, decorationColor: String?) { + val underlineColor = decorationColor?.let { parseColorSafe(it) } when (line) { TextDecorationLine.UNDERLINE -> builder.setSpan(UnderlineSpan(), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) TextDecorationLine.LINE_THROUGH -> builder.setSpan(StrikethroughSpan(), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) diff --git a/example/src/screens/HtmlScreen.tsx b/example/src/screens/HtmlScreen.tsx index 69ba2a6..3f6f627 100644 --- a/example/src/screens/HtmlScreen.tsx +++ b/example/src/screens/HtmlScreen.tsx @@ -107,9 +107,12 @@ export function HtmlScreen() {

Green bold text from inline style

Custom font size and line height

-

Centered text

-

uppercase text

-

Purple underline

`} +
+

Centered text

+

uppercase text

+

Purple underline

+
+`}
diff --git a/example/src/screens/PlainTextScreen.tsx b/example/src/screens/PlainTextScreen.tsx index 7934810..c2f02a9 100644 --- a/example/src/screens/PlainTextScreen.tsx +++ b/example/src/screens/PlainTextScreen.tsx @@ -1,5 +1,5 @@ import React, { useLayoutEffect, useMemo } from 'react'; -import { ScrollView, StatusBar, StyleSheet, Text, View } from 'react-native'; +import { ScrollView, StatusBar, Text, View } from 'react-native'; import { NitroText } from 'react-native-nitro-text'; import { styles } from './styles'; import { NitroModules } from 'react-native-nitro-modules';