diff --git a/android/CMakeLists.txt b/android/CMakeLists.txt new file mode 100644 index 0000000..fd63c0d --- /dev/null +++ b/android/CMakeLists.txt @@ -0,0 +1,45 @@ +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/NitroHtmlUtils.cpp + ../cpp/NitroHtmlUtils.hpp + ../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..cb25ab0 --- /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" + } +} 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/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 new file mode 100755 index 0000000..9e68108 --- /dev/null +++ b/android/src/main/java/com/nitrotext/HybridNitroText.kt @@ -0,0 +1,190 @@ +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 renderer: Renderer? + get() = null + set(value) { + // HTML parsing is now done in JS/TS layer, renderer is ignored + } + + 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 menus: Array? + get() = null + set(value) { + view.customMenus = 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/NitroTextImpl.kt b/android/src/main/java/com/nitrotext/NitroTextImpl.kt new file mode 100644 index 0000000..2674e94 --- /dev/null +++ b/android/src/main/java/com/nitrotext/NitroTextImpl.kt @@ -0,0 +1,365 @@ +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.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.* +import androidx.core.graphics.toColorInt +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 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() + + // 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 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, textDecorationColor) + 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 hasLinks = false + 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 + + // 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) + }} + // 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) + } + // 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, frag.textDecorationColor) + 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 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) + } + } + } + } + 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?, 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) + 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 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) { + 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) + } +} 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..14d477e --- /dev/null +++ b/android/src/main/java/com/nitrotext/NitroTextView.kt @@ -0,0 +1,314 @@ +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.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 { + 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) + // 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 + set(value) { + 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 { configureActionModeCallback(it) } + } + + 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() + + 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.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 + + // 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() + } + + // 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/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/UrlSpanNoUnderline.kt b/android/src/main/java/com/nitrotext/spans/UrlSpanNoUnderline.kt new file mode 100644 index 0000000..e7c49ef --- /dev/null +++ b/android/src/main/java/com/nitrotext/spans/UrlSpanNoUnderline.kt @@ -0,0 +1,12 @@ +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) { + // Don't call super.updateDrawState() to avoid default link color + // This allows custom colors from ForegroundColorSpan to be applied + ds.isUnderlineText = false + } +} 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/NitroTextComponentDescriptor.cpp b/cpp/NitroTextComponentDescriptor.cpp index 738744f..e156957 100644 --- a/cpp/NitroTextComponentDescriptor.cpp +++ b/cpp/NitroTextComponentDescriptor.cpp @@ -3,8 +3,8 @@ // Shared implementation for custom ComponentDescriptor // -#include "NitroTextComponentDescriptor.hpp" #include +#include "NitroTextComponentDescriptor.hpp" using namespace facebook; using namespace margelo::nitro::nitrotext::views; @@ -38,6 +38,7 @@ NitroTextComponentDescriptor::NitroTextComponentDescriptor(const react::Componen // 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 index dda54b1..4a7dd97 100644 --- a/cpp/NitroTextComponentDescriptor.hpp +++ b/cpp/NitroTextComponentDescriptor.hpp @@ -1,31 +1,30 @@ // -// NitroTextComponentDescriptor.hpp +// NitroTextComponentDescriptor.hpp (shared) // Custom, non-generated ComponentDescriptor for NitroText // #pragma once -#include "../cpp/NitroTextShadowNode.hpp" +#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`. + * The Component Descriptor for the "NitroText" View. */ - 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 + 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 9b2fe8b..5c8605c 100644 --- a/cpp/NitroTextShadowNode.cpp +++ b/cpp/NitroTextShadowNode.cpp @@ -5,11 +5,17 @@ #include "NitroTextShadowNode.hpp" #include "NitroTextUtil.hpp" +#include "NitroHtmlUtils.hpp" + #include #include #include #include +#if defined(__ANDROID__) +#include +#endif + #include #include @@ -17,6 +23,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 { namespace { 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/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/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 55e0943..c2f02a9 100644 --- a/example/src/screens/PlainTextScreen.tsx +++ b/example/src/screens/PlainTextScreen.tsx @@ -1,5 +1,5 @@ -import React, { useMemo } from 'react'; -import { ScrollView, StyleSheet, Text, View } from 'react-native'; +import React, { useLayoutEffect, useMemo } from 'react'; +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'; @@ -13,6 +13,10 @@ export function PlainTextScreen() { [], ); + useLayoutEffect(() => { + StatusBar.setBarStyle('dark-content'); + }, []); + return ( {/* Header Section */} @@ -30,12 +34,15 @@ export function PlainTextScreen() { 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! @@ -44,7 +51,7 @@ export function PlainTextScreen() { {/* 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.{' '} @@ -53,6 +60,17 @@ export function PlainTextScreen() { + + + 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 */} @@ -100,14 +118,14 @@ export function PlainTextScreen() { 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. 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/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..9290e23 --- /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.hpp" +#include "JFunc_void_TextLayoutEvent.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_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/margelo/nitro/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..b79c6d0 --- /dev/null +++ b/nitrogen/generated/android/c++/JFragment.hpp @@ -0,0 +1,130 @@ +/// +/// 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); + 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, + 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, + linkUrl != nullptr ? std::make_optional(linkUrl->toStdString()) : 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) { + 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, + 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, + value.linkUrl.has_value() ? jni::make_jstring(value.linkUrl.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..f1b7802 --- /dev/null +++ b/nitrogen/generated/android/c++/JFunc_void.hpp @@ -0,0 +1,75 @@ +/// +/// 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 +#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<...>`) + */ + class 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..b03ff0c --- /dev/null +++ b/nitrogen/generated/android/c++/JFunc_void_TextLayoutEvent.hpp @@ -0,0 +1,81 @@ +/// +/// 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 +#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<...>`) + */ + 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); + } + + 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..f7680ee --- /dev/null +++ b/nitrogen/generated/android/c++/JHybridNitroTextSpec.cpp @@ -0,0 +1,455 @@ +/// +/// 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 `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. +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 "Renderer.hpp" +#include "JRenderer.hpp" +#include "EllipsizeMode.hpp" +#include "JEllipsizeMode.hpp" +#include "LineBreakStrategyIOS.hpp" +#include "JLineBreakStrategyIOS.hpp" +#include "DynamicTypeRamp.hpp" +#include "JDynamicTypeRamp.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" + +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); + } + + 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"); + 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]; + auto __elementJni = JFragment::fromCpp(__element); + __array->setElement(__i, *__elementJni); + } + 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() ? JRenderer::fromCpp(renderer.value()) : 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::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); + 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 JNICallable(std::move(__resultRef)); + } + }()) : 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 JNICallable(std::move(__resultRef)); + } + }()) : 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 JNICallable(std::move(__resultRef)); + } + }()) : 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 JNICallable(std::move(__resultRef)); + } + }()) : 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..996d9d4 --- /dev/null +++ b/nitrogen/generated/android/c++/JHybridNitroTextSpec.hpp @@ -0,0 +1,126 @@ +/// +/// 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; + std::string toString() 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 getRenderer() override; + void setRenderer(std::optional renderer) 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> getMenus() override; + void setMenus(const std::optional>& menus) 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++/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++/JRenderer.hpp b/nitrogen/generated/android/c++/JRenderer.hpp new file mode 100644 index 0000000..ef80c85 --- /dev/null +++ b/nitrogen/generated/android/c++/JRenderer.hpp @@ -0,0 +1,59 @@ +/// +/// JRenderer.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 "Renderer.hpp" + +namespace margelo::nitro::nitrotext { + + using namespace facebook; + + /** + * The C++ JNI bridge between the C++ enum "Renderer" and the the Kotlin enum "Renderer". + */ + struct JRenderer final: public jni::JavaClass { + public: + static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/nitrotext/Renderer;"; + + public: + /** + * Convert this Java/Kotlin-based enum to the C++ enum Renderer. + */ + [[maybe_unused]] + [[nodiscard]] + Renderer 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(Renderer value) { + static const auto clazz = javaClassStatic(); + static const auto fieldHTML = clazz->getStaticField("HTML"); + static const auto fieldPLAINTEXT = clazz->getStaticField("PLAINTEXT"); + + switch (value) { + 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 + "!"); + } + } + }; + +} // 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..d323c2d --- /dev/null +++ b/nitrogen/generated/android/c++/JTextLayout.hpp @@ -0,0 +1,89 @@ +/// +/// 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) { + 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, + 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..a3ae4f9 --- /dev/null +++ b/nitrogen/generated/android/c++/JTextLayoutEvent.hpp @@ -0,0 +1,78 @@ +/// +/// 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) { + 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]; + auto __elementJni = JTextLayout::fromCpp(__element); + __array->setElement(__i, *__elementJni); + } + 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..00c4e4a --- /dev/null +++ b/nitrogen/generated/android/c++/views/JHybridNitroTextStateUpdater.cpp @@ -0,0 +1,176 @@ +/// +/// 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.renderer.isDirty) { + view->setRenderer(props.renderer.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.menus.isDirty) { + view->setMenus(props.menus.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..e693831 --- /dev/null +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/Fragment.kt @@ -0,0 +1,83 @@ +/// +/// 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 + + +/** + * Represents the JavaScript object/struct "Fragment". + */ +@DoNotStrip +@Keep +data class Fragment( + @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?, + @DoNotStrip + @Keep + val linkUrl: String? +) { + /* primary constructor */ + + private companion object { + /** + * Constructor called from C++ + */ + @DoNotStrip + @Keep + @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 new file mode 100644 index 0000000..cb01e75 --- /dev/null +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/Func_void.kt @@ -0,0 +1,80 @@ +/// +/// 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 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..3534207 --- /dev/null +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/Func_void_TextLayoutEvent.kt @@ -0,0 +1,80 @@ +/// +/// 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 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..ceb2d86 --- /dev/null +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/HybridNitroTextSpec.kt @@ -0,0 +1,271 @@ +/// +/// 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.views.HybridView + +/** + * 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) + } + + // Default implementation of `HybridObject.toString()` + override fun toString(): String { + return "[HybridObject NitroText]" + } + + // Properties + @get:DoNotStrip + @get:Keep + @set:DoNotStrip + @set:Keep + abstract var fragments: Array? + + @get:DoNotStrip + @get:Keep + @set:DoNotStrip + @set:Keep + abstract var renderer: Renderer? + + @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? + + @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? + @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 { + protected 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/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/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/Renderer.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/Renderer.kt new file mode 100644 index 0000000..e2bbe2d --- /dev/null +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/Renderer.kt @@ -0,0 +1,21 @@ +/// +/// Renderer.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 "Renderer". + */ +@DoNotStrip +@Keep +enum class Renderer(@DoNotStrip @Keep val value: Int) { + HTML(0), + PLAINTEXT(1); +} 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..dc2c49b --- /dev/null +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/TextLayout.kt @@ -0,0 +1,62 @@ +/// +/// 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 + + +/** + * Represents the JavaScript object/struct "TextLayout". + */ +@DoNotStrip +@Keep +data class TextLayout( + @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 +) { + /* 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 new file mode 100644 index 0000000..bbc7b01 --- /dev/null +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotext/TextLayoutEvent.kt @@ -0,0 +1,38 @@ +/// +/// 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 + + +/** + * Represents the JavaScript object/struct "TextLayoutEvent". + */ +@DoNotStrip +@Keep +data class TextLayoutEvent( + @DoNotStrip + @Keep + val lines: Array +) { + /* primary constructor */ + + private companion object { + /** + * Constructor called from C++ + */ + @DoNotStrip + @Keep + @Suppress("unused") + @JvmStatic + private fun fromCpp(lines: Array): TextLayoutEvent { + return TextLayoutEvent(lines) + } + } +} 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..ec18112 --- /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.HybridNitroText + +/** + * 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/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 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 (