@matiks/rn-stroke-text 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/NitroRnStrokeText.podspec +31 -0
  2. package/README.md +38 -0
  3. package/android/CMakeLists.txt +29 -0
  4. package/android/build.gradle +138 -0
  5. package/android/fix-prefab.gradle +51 -0
  6. package/android/gradle.properties +5 -0
  7. package/android/src/main/AndroidManifest.xml +2 -0
  8. package/android/src/main/cpp/cpp-adapter.cpp +6 -0
  9. package/android/src/main/java/com/margelo/nitro/rnstroketext/FontUtil.kt +48 -0
  10. package/android/src/main/java/com/margelo/nitro/rnstroketext/HybridMatiksStrokeText.kt +80 -0
  11. package/android/src/main/java/com/margelo/nitro/rnstroketext/NitroRnStrokeTextPackage.kt +28 -0
  12. package/android/src/main/java/com/margelo/nitro/rnstroketext/StrokeTextView.kt +348 -0
  13. package/ios/Bridge.h +8 -0
  14. package/ios/HybridMatiksStrokeText.swift +97 -0
  15. package/ios/StrokeTextView.swift +174 -0
  16. package/ios/StrokedTextLabel.swift +62 -0
  17. package/lib/index.d.ts +10 -0
  18. package/lib/index.js +23 -0
  19. package/lib/specs/Stroke.nitro.d.ts +23 -0
  20. package/lib/specs/Stroke.nitro.js +1 -0
  21. package/nitro.json +24 -0
  22. package/nitrogen/generated/.gitattributes +1 -0
  23. package/nitrogen/generated/android/NitroRnStrokeText+autolinking.cmake +83 -0
  24. package/nitrogen/generated/android/NitroRnStrokeText+autolinking.gradle +27 -0
  25. package/nitrogen/generated/android/NitroRnStrokeTextOnLoad.cpp +46 -0
  26. package/nitrogen/generated/android/NitroRnStrokeTextOnLoad.hpp +25 -0
  27. package/nitrogen/generated/android/c++/JDimensions.hpp +61 -0
  28. package/nitrogen/generated/android/c++/JHybridMatiksStrokeTextSpec.cpp +156 -0
  29. package/nitrogen/generated/android/c++/JHybridMatiksStrokeTextSpec.hpp +85 -0
  30. package/nitrogen/generated/android/c++/JTextAlign.hpp +61 -0
  31. package/nitrogen/generated/android/c++/views/JHybridMatiksStrokeTextStateUpdater.cpp +92 -0
  32. package/nitrogen/generated/android/c++/views/JHybridMatiksStrokeTextStateUpdater.hpp +49 -0
  33. package/nitrogen/generated/android/kotlin/com/margelo/nitro/rnstroketext/Dimensions.kt +41 -0
  34. package/nitrogen/generated/android/kotlin/com/margelo/nitro/rnstroketext/HybridMatiksStrokeTextSpec.kt +115 -0
  35. package/nitrogen/generated/android/kotlin/com/margelo/nitro/rnstroketext/NitroRnStrokeTextOnLoad.kt +35 -0
  36. package/nitrogen/generated/android/kotlin/com/margelo/nitro/rnstroketext/TextAlign.kt +24 -0
  37. package/nitrogen/generated/android/kotlin/com/margelo/nitro/rnstroketext/views/HybridMatiksStrokeTextManager.kt +56 -0
  38. package/nitrogen/generated/android/kotlin/com/margelo/nitro/rnstroketext/views/HybridMatiksStrokeTextStateUpdater.kt +23 -0
  39. package/nitrogen/generated/ios/NitroRnStrokeText+autolinking.rb +60 -0
  40. package/nitrogen/generated/ios/NitroRnStrokeText-Swift-Cxx-Bridge.cpp +33 -0
  41. package/nitrogen/generated/ios/NitroRnStrokeText-Swift-Cxx-Bridge.hpp +119 -0
  42. package/nitrogen/generated/ios/NitroRnStrokeText-Swift-Cxx-Umbrella.hpp +51 -0
  43. package/nitrogen/generated/ios/NitroRnStrokeTextAutolinking.mm +33 -0
  44. package/nitrogen/generated/ios/NitroRnStrokeTextAutolinking.swift +25 -0
  45. package/nitrogen/generated/ios/c++/HybridMatiksStrokeTextSpecSwift.cpp +11 -0
  46. package/nitrogen/generated/ios/c++/HybridMatiksStrokeTextSpecSwift.hpp +157 -0
  47. package/nitrogen/generated/ios/c++/views/HybridMatiksStrokeTextComponent.mm +147 -0
  48. package/nitrogen/generated/ios/swift/Dimensions.swift +35 -0
  49. package/nitrogen/generated/ios/swift/HybridMatiksStrokeTextSpec.swift +65 -0
  50. package/nitrogen/generated/ios/swift/HybridMatiksStrokeTextSpec_cxx.swift +341 -0
  51. package/nitrogen/generated/ios/swift/TextAlign.swift +44 -0
  52. package/nitrogen/generated/shared/c++/Dimensions.hpp +87 -0
  53. package/nitrogen/generated/shared/c++/HybridMatiksStrokeTextSpec.cpp +41 -0
  54. package/nitrogen/generated/shared/c++/HybridMatiksStrokeTextSpec.hpp +87 -0
  55. package/nitrogen/generated/shared/c++/TextAlign.hpp +80 -0
  56. package/nitrogen/generated/shared/c++/views/HybridMatiksStrokeTextComponent.cpp +196 -0
  57. package/nitrogen/generated/shared/c++/views/HybridMatiksStrokeTextComponent.hpp +118 -0
  58. package/nitrogen/generated/shared/json/MatiksStrokeTextConfig.json +19 -0
  59. package/package.json +107 -0
  60. package/react-native.config.js +16 -0
  61. package/src/index.tsx +44 -0
  62. package/src/specs/Stroke.nitro.ts +34 -0
@@ -0,0 +1,348 @@
1
+ package com.margelo.nitro.rnstroketext
2
+
3
+ import android.content.Context
4
+ import android.graphics.Canvas
5
+ import android.graphics.Color
6
+ import android.graphics.Paint
7
+ import android.graphics.Typeface
8
+ import android.text.Layout
9
+ import android.text.StaticLayout
10
+ import android.text.TextPaint
11
+ import android.text.TextUtils
12
+ import android.util.TypedValue
13
+ import android.view.View
14
+ import kotlin.math.ceil
15
+ import kotlin.math.max
16
+
17
+ class StrokeTextView(
18
+ context: Context
19
+ ) : View(context) {
20
+
21
+ // ─────────────────────────────────────────────
22
+ // Props
23
+ // ─────────────────────────────────────────────
24
+
25
+ private var text: String = ""
26
+ private var fontSizePx: Float = sp(14f)
27
+ private var textColor: Int = Color.BLACK
28
+ private var strokeColor: Int = Color.WHITE
29
+ private var strokeWidthPx: Float = dp(1f)
30
+ private var fontFamily: String = "sans-serif"
31
+ private var numberOfLines: Int = 0
32
+ private var ellipsis: Boolean = false
33
+ private var alignment: Layout.Alignment = Layout.Alignment.ALIGN_CENTER
34
+ private var customWidthPx: Float = 0f
35
+
36
+ // ─────────────────────────────────────────────
37
+ // Paints
38
+ // ─────────────────────────────────────────────
39
+
40
+ private val textPaint = TextPaint(Paint.ANTI_ALIAS_FLAG).apply {
41
+ style = Paint.Style.FILL
42
+ }
43
+
44
+ private val strokePaint = TextPaint(Paint.ANTI_ALIAS_FLAG).apply {
45
+ style = Paint.Style.STROKE
46
+ strokeJoin = Paint.Join.ROUND
47
+ strokeCap = Paint.Cap.ROUND
48
+ }
49
+
50
+ // ─────────────────────────────────────────────
51
+ // Layout
52
+ // ─────────────────────────────────────────────
53
+
54
+ private var textLayout: StaticLayout? = null
55
+ private var strokeLayout: StaticLayout? = null
56
+ private var layoutDirty = true
57
+
58
+ private val fontCache = HashMap<String, Typeface?>()
59
+
60
+ // ─────────────────────────────────────────────
61
+ // Measurement (RN / Nitro safe)
62
+ // ─────────────────────────────────────────────
63
+
64
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
65
+ updatePaints()
66
+
67
+ val measuredWidth = resolveMeasuredWidth(widthMeasureSpec)
68
+ ensureLayout(measuredWidth)
69
+
70
+ val desiredHeight = (textLayout?.height ?: 0).coerceAtLeast(1)
71
+
72
+ setMeasuredDimension(
73
+ measuredWidth,
74
+ resolveSize(desiredHeight, heightMeasureSpec)
75
+ )
76
+ }
77
+
78
+ private fun resolveMeasuredWidth(spec: Int): Int {
79
+ val mode = MeasureSpec.getMode(spec)
80
+ val size = MeasureSpec.getSize(spec)
81
+
82
+ // Width explicitly provided from JS prop
83
+ if (customWidthPx > 0f) {
84
+ return ceil(customWidthPx).toInt().coerceAtLeast(1)
85
+ }
86
+
87
+ // Exact width from Yoga
88
+ if (mode == MeasureSpec.EXACTLY && size > 0) {
89
+ return size
90
+ }
91
+
92
+ // Intrinsic width from text
93
+ val textWidth = ceil(measureTextWidth()).toInt().coerceAtLeast(1)
94
+
95
+ return when (mode) {
96
+ MeasureSpec.AT_MOST -> minOf(size, textWidth)
97
+ else -> textWidth
98
+ }
99
+ }
100
+
101
+ // ─────────────────────────────────────────────
102
+ // Layout creation
103
+ // ─────────────────────────────────────────────
104
+
105
+ private fun updatePaints() {
106
+ val typeface = getFont(fontFamily)
107
+
108
+ textPaint.apply {
109
+ this.typeface = typeface
110
+ textSize = fontSizePx
111
+ color = textColor
112
+ }
113
+
114
+ strokePaint.apply {
115
+ this.typeface = typeface
116
+ textSize = fontSizePx
117
+ strokeWidth = strokeWidthPx
118
+ color = strokeColor
119
+ }
120
+ }
121
+
122
+ private fun ensureLayout(width: Int) {
123
+ if (!layoutDirty && textLayout != null) return
124
+
125
+ val safeWidth = width.coerceAtLeast(1)
126
+ updatePaints()
127
+
128
+ var displayText: CharSequence =
129
+ if (ellipsis) {
130
+ TextUtils.ellipsize(
131
+ text,
132
+ textPaint,
133
+ safeWidth.toFloat(),
134
+ TextUtils.TruncateAt.END
135
+ )
136
+ } else {
137
+ text
138
+ }
139
+
140
+ var layout = StaticLayout(
141
+ displayText,
142
+ textPaint,
143
+ safeWidth,
144
+ alignment,
145
+ 1f,
146
+ 0f,
147
+ false
148
+ )
149
+
150
+ if (numberOfLines > 0 && layout.lineCount > numberOfLines) {
151
+ val end = layout.getLineEnd(numberOfLines - 1)
152
+ displayText = displayText.subSequence(0, end)
153
+
154
+ layout = StaticLayout(
155
+ displayText,
156
+ textPaint,
157
+ safeWidth,
158
+ alignment,
159
+ 1f,
160
+ 0f,
161
+ false
162
+ )
163
+ }
164
+
165
+ textLayout = layout
166
+ strokeLayout = StaticLayout(
167
+ displayText,
168
+ strokePaint,
169
+ safeWidth,
170
+ alignment,
171
+ 1f,
172
+ 0f,
173
+ false
174
+ )
175
+
176
+ layoutDirty = false
177
+ }
178
+
179
+ fun getTextDimensions(): Pair<Double, Double> {
180
+ updatePaints()
181
+
182
+ // Ensure we have a valid layout for measurement.
183
+ // If width is 0/unspecified, we measure intrinsic width.
184
+ val measureWidth = if (customWidthPx > 0f) {
185
+ ceil(customWidthPx).toInt()
186
+ } else {
187
+ ceil(measureTextWidth()).toInt()
188
+ }
189
+
190
+ // We create a temporary layout if needed to get accurate height for the given width
191
+ // But since ensureLayout caches the layout based on last width, we might want to just ensure layout exists.
192
+ // For accurate "intrinsic" measurement, we usually want the width to be the text width if not constrained.
193
+
194
+ ensureLayout(measureWidth)
195
+
196
+ val w = textLayout?.width?.toDouble() ?: 0.0
197
+ val h = textLayout?.height?.toDouble() ?: 0.0
198
+
199
+ return Pair(w, h)
200
+ }
201
+
202
+ private fun measureTextWidth(): Float {
203
+ var maxWidth = 0f
204
+ for (line in text.split("\n")) {
205
+ maxWidth = max(maxWidth, textPaint.measureText(line))
206
+ }
207
+ return maxWidth + strokeWidthPx * 2
208
+ }
209
+
210
+ // ─────────────────────────────────────────────
211
+ // Drawing
212
+ // ─────────────────────────────────────────────
213
+
214
+ override fun onDraw(canvas: Canvas) {
215
+ super.onDraw(canvas)
216
+ ensureLayout(width)
217
+ strokeLayout?.draw(canvas)
218
+ textLayout?.draw(canvas)
219
+ }
220
+
221
+ // ─────────────────────────────────────────────
222
+ // Props setters (Nitro)
223
+ // ─────────────────────────────────────────────
224
+
225
+ fun setText(value: String) {
226
+ if (text != value) {
227
+ text = value
228
+ invalidateLayout()
229
+ }
230
+ }
231
+
232
+ fun setFontSize(value: Float) {
233
+ val px = sp(value)
234
+ if (fontSizePx != px) {
235
+ fontSizePx = px
236
+ invalidateLayout()
237
+ }
238
+ }
239
+
240
+ fun setTextColor(value: String) {
241
+ val c = parseColor(value)
242
+ if (textColor != c) {
243
+ textColor = c
244
+ invalidate()
245
+ }
246
+ }
247
+
248
+ fun setStrokeColor(value: String) {
249
+ val c = parseColor(value)
250
+ if (strokeColor != c) {
251
+ strokeColor = c
252
+ invalidate()
253
+ }
254
+ }
255
+
256
+ fun setStrokeWidth(value: Float) {
257
+ val px = dp(value)
258
+ if (strokeWidthPx != px) {
259
+ strokeWidthPx = px
260
+ invalidateLayout()
261
+ }
262
+ }
263
+
264
+ fun setFontFamily(value: String) {
265
+ if (fontFamily != value) {
266
+ fontFamily = value
267
+ invalidateLayout()
268
+ }
269
+ }
270
+
271
+ fun setTextAlignment(value: String) {
272
+ val newAlignment = when (value) {
273
+ "left" -> Layout.Alignment.ALIGN_NORMAL
274
+ "right" -> Layout.Alignment.ALIGN_OPPOSITE
275
+ "center" -> Layout.Alignment.ALIGN_CENTER
276
+ else -> alignment
277
+ }
278
+ if (alignment != newAlignment) {
279
+ alignment = newAlignment
280
+ invalidateLayout()
281
+ }
282
+ }
283
+
284
+ fun setNumberOfLines(value: Int) {
285
+ if (numberOfLines != value) {
286
+ numberOfLines = value
287
+ invalidateLayout()
288
+ }
289
+ }
290
+
291
+ fun setEllipsis(value: Boolean) {
292
+ if (ellipsis != value) {
293
+ ellipsis = value
294
+ invalidateLayout()
295
+ }
296
+ }
297
+
298
+ fun setCustomWidth(value: Float) {
299
+ val px = dp(value)
300
+ if (customWidthPx != px) {
301
+ customWidthPx = px
302
+ invalidateLayout()
303
+ }
304
+ }
305
+
306
+ // ─────────────────────────────────────────────
307
+ // Helpers
308
+ // ─────────────────────────────────────────────
309
+
310
+ private fun invalidateLayout() {
311
+ layoutDirty = true
312
+ requestLayout()
313
+ invalidate()
314
+ }
315
+
316
+ private fun sp(v: Float): Float =
317
+ TypedValue.applyDimension(
318
+ TypedValue.COMPLEX_UNIT_SP,
319
+ v,
320
+ resources.displayMetrics
321
+ )
322
+
323
+ private fun dp(v: Float): Float =
324
+ TypedValue.applyDimension(
325
+ TypedValue.COMPLEX_UNIT_DIP,
326
+ v,
327
+ resources.displayMetrics
328
+ )
329
+
330
+ private fun parseColor(color: String): Int =
331
+ try {
332
+ Color.parseColor(color)
333
+ } catch (_: Exception) {
334
+ Color.BLACK
335
+ }
336
+
337
+ private fun getFont(fontFamily: String): Typeface? {
338
+ return fontCache[fontFamily] ?: run {
339
+ val tf = FontUtil.getFont(context, fontFamily)
340
+ fontCache[fontFamily] = tf
341
+ tf
342
+ }
343
+ }
344
+
345
+ init {
346
+ setWillNotDraw(false)
347
+ }
348
+ }
package/ios/Bridge.h ADDED
@@ -0,0 +1,8 @@
1
+ //
2
+ // Bridge.h
3
+ // NitroRnStrokeText
4
+ //
5
+ // Created by Marc Rousavy on 22.07.24.
6
+ //
7
+
8
+ #pragma once
@@ -0,0 +1,97 @@
1
+ import Foundation
2
+ import NitroModules
3
+ import UIKit
4
+
5
+ class HybridMatiksStrokeText: HybridMatiksStrokeTextSpec {
6
+ var view: UIView {
7
+ return strokeTextView
8
+ }
9
+
10
+ private let strokeTextView: StrokeTextView
11
+
12
+ public override init() {
13
+ self.strokeTextView = StrokeTextView(frame: .zero)
14
+ super.init()
15
+ }
16
+
17
+ // MARK: - Props
18
+
19
+ var width: Double? = 0 {
20
+ didSet {
21
+ strokeTextView.width = NSNumber(value: width ?? 0)
22
+ }
23
+ }
24
+
25
+ var text: String = "" {
26
+ didSet {
27
+ strokeTextView.text = text
28
+ }
29
+ }
30
+
31
+ var fontSize: Double? = 0 {
32
+ didSet {
33
+ strokeTextView.fontSize = NSNumber(value: fontSize ?? 0)
34
+ }
35
+ }
36
+
37
+ var color: String? = "" {
38
+ didSet {
39
+ strokeTextView.color = color ?? "#000000"
40
+ }
41
+ }
42
+
43
+ var strokeColor: String? = "" {
44
+ didSet {
45
+ strokeTextView.strokeColor = strokeColor ?? "#FFFFFF"
46
+ }
47
+ }
48
+
49
+ var strokeWidth: Double? = 0 {
50
+ didSet {
51
+ strokeTextView.strokeWidth = NSNumber(value: strokeWidth ?? 0)
52
+ }
53
+ }
54
+
55
+ var fontFamily: String? = "" {
56
+ didSet {
57
+ strokeTextView.fontFamily = fontFamily ?? ""
58
+ }
59
+ }
60
+
61
+ var align: TextAlign? = .center {
62
+ didSet {
63
+ switch align {
64
+ case .left: strokeTextView.align = "left"
65
+ case .right: strokeTextView.align = "right"
66
+ case .center: strokeTextView.align = "center"
67
+ case .none: strokeTextView.align = "center"
68
+ }
69
+ }
70
+ }
71
+
72
+ var numberOfLines: Double? = 0 {
73
+ didSet {
74
+ strokeTextView.numberOfLines = NSNumber(value: numberOfLines ?? 0)
75
+ }
76
+ }
77
+
78
+ var ellipsis: Bool? = false {
79
+ didSet {
80
+ strokeTextView.ellipsis = ellipsis ?? false
81
+ }
82
+ }
83
+
84
+ // MARK: - Methods
85
+
86
+ func measureDimensions() -> Dimensions {
87
+ // StrokeTextView.measureDimensions returns [String: NSNumber]
88
+ // We need to return Dimensions struct which is generated by Nitro
89
+
90
+ let dims = strokeTextView.measureDimensions()
91
+ let w = dims["width"]?.doubleValue ?? 0.0
92
+ let h = dims["height"]?.doubleValue ?? 0.0
93
+
94
+ // Assuming Dimensions is a generated struct with init(width: Double, height: Double)
95
+ return Dimensions(width: w, height: h)
96
+ }
97
+ }
@@ -0,0 +1,174 @@
1
+ import UIKit
2
+ import NitroModules
3
+
4
+ class StrokeTextView: UIView {
5
+
6
+ private let label = StrokedTextLabel()
7
+ private var fontCache: [String: UIFont] = [:]
8
+
9
+ override init(frame: CGRect) {
10
+ super.init(frame: frame)
11
+
12
+ label.translatesAutoresizingMaskIntoConstraints = false
13
+ addSubview(label)
14
+
15
+ NSLayoutConstraint.activate([
16
+ label.centerXAnchor.constraint(equalTo: centerXAnchor),
17
+ label.centerYAnchor.constraint(equalTo: centerYAnchor)
18
+ ])
19
+ }
20
+
21
+ required init?(coder: NSCoder) {
22
+ fatalError("init(coder:) has not been implemented")
23
+ }
24
+
25
+ // MARK: - Layout (THIS replaces UIManager.setSize)
26
+
27
+ override var intrinsicContentSize: CGSize {
28
+ return label.intrinsicContentSize
29
+ }
30
+
31
+ // MARK: - Props (Nitro-style)
32
+
33
+ @objc var text: String = "" {
34
+ didSet {
35
+ label.text = text
36
+ invalidateIntrinsicContentSize()
37
+ }
38
+ }
39
+
40
+ @objc var fontSize: NSNumber = 14 {
41
+ didSet {
42
+ label.font = label.font.withSize(CGFloat(truncating: fontSize))
43
+ invalidateIntrinsicContentSize()
44
+ }
45
+ }
46
+
47
+ @objc var fontFamily: String = "Helvetica" {
48
+ didSet {
49
+ let key = "\(fontFamily)-\(fontSize)"
50
+ if let cached = fontCache[key] {
51
+ label.font = cached
52
+ } else {
53
+ let size = CGFloat(truncating: fontSize)
54
+ let font =
55
+ UIFont(name: fontFamily, size: size)
56
+ ?? UIFont.systemFont(ofSize: size)
57
+
58
+ fontCache[key] = font
59
+ label.font = font
60
+ }
61
+ invalidateIntrinsicContentSize()
62
+ }
63
+ }
64
+
65
+ @objc var color: String = "#000000" {
66
+ didSet {
67
+ label.textColor = colorStringToUIColor(color)
68
+ }
69
+ }
70
+
71
+ @objc var strokeColor: String = "#FFFFFF" {
72
+ didSet {
73
+ label.outlineColor = colorStringToUIColor(strokeColor)
74
+ }
75
+ }
76
+
77
+ @objc var strokeWidth: NSNumber = 0 {
78
+ didSet {
79
+ label.outlineWidth = CGFloat(truncating: strokeWidth)
80
+ }
81
+ }
82
+
83
+ @objc var align: String = "center" {
84
+ didSet {
85
+ label.align =
86
+ align == "left" ? .left :
87
+ align == "right" ? .right : .center
88
+ }
89
+ }
90
+
91
+ @objc var ellipsis: Bool = false {
92
+ didSet {
93
+ label.ellipsis = ellipsis
94
+ invalidateIntrinsicContentSize()
95
+ }
96
+ }
97
+
98
+ @objc var numberOfLines: NSNumber = 0 {
99
+ didSet {
100
+ label.numberOfLines = Int(truncating: numberOfLines)
101
+ invalidateIntrinsicContentSize()
102
+ }
103
+ }
104
+
105
+ @objc var width: NSNumber = 0 {
106
+ didSet {
107
+ label.customWidth = CGFloat(truncating: width)
108
+ invalidateIntrinsicContentSize()
109
+ }
110
+ }
111
+
112
+ // MARK: - Hybrid Methods (optional but powerful)
113
+
114
+ @objc func measureDimensions() -> [String: NSNumber] {
115
+ let size = intrinsicContentSize
116
+ return [
117
+ "width": NSNumber(value: Double(size.width)),
118
+ "height": NSNumber(value: Double(size.height))
119
+ ]
120
+ }
121
+
122
+ // MARK: - Helpers
123
+
124
+ private func colorStringToUIColor(_ colorString: String) -> UIColor {
125
+ let cString = colorString.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
126
+
127
+ // Handle named colors
128
+ switch cString {
129
+ case "black": return .black
130
+ case "darkgray", "darkgrey": return .darkGray
131
+ case "lightgray", "lightgrey": return .lightGray
132
+ case "white": return .white
133
+ case "gray", "grey": return .gray
134
+ case "red": return .red
135
+ case "green": return .green
136
+ case "blue": return .blue
137
+ case "cyan": return .cyan
138
+ case "yellow": return .yellow
139
+ case "magenta": return .magenta
140
+ case "orange": return .orange
141
+ case "purple": return .purple
142
+ case "brown": return .brown
143
+ case "clear": return .clear
144
+ default: break
145
+ }
146
+
147
+ // Handle Hex
148
+ var hex = cString.uppercased()
149
+ if hex.hasPrefix("#") {
150
+ hex.remove(at: hex.startIndex)
151
+ }
152
+
153
+ var rgbValue: UInt64 = 0
154
+ Scanner(string: hex).scanHexInt64(&rgbValue)
155
+
156
+ if hex.count == 6 {
157
+ return UIColor(
158
+ red: CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0,
159
+ green: CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0,
160
+ blue: CGFloat(rgbValue & 0x0000FF) / 255.0,
161
+ alpha: 1.0
162
+ )
163
+ } else if hex.count == 8 {
164
+ return UIColor(
165
+ red: CGFloat((rgbValue & 0xFF000000) >> 24) / 255.0,
166
+ green: CGFloat((rgbValue & 0x00FF0000) >> 16) / 255.0,
167
+ blue: CGFloat((rgbValue & 0x0000FF00) >> 8) / 255.0,
168
+ alpha: CGFloat(rgbValue & 0x000000FF) / 255.0
169
+ )
170
+ }
171
+
172
+ return .black
173
+ }
174
+ }
@@ -0,0 +1,62 @@
1
+ import UIKit
2
+
3
+ class StrokedTextLabel: UILabel {
4
+ required init?(coder: NSCoder) {
5
+ fatalError("init(coder:) has not been implemented")
6
+ }
7
+
8
+ override init(frame: CGRect) {
9
+ super.init(frame: frame)
10
+ self.numberOfLines = 0
11
+ }
12
+
13
+ private var textInsets: UIEdgeInsets = .zero
14
+
15
+ public func updateTextInsets() {
16
+ textInsets = UIEdgeInsets(top: outlineWidth, left: outlineWidth, bottom: outlineWidth, right: outlineWidth)
17
+ }
18
+
19
+ var outlineWidth: CGFloat = 0
20
+ var outlineColor: UIColor = .clear
21
+ var align: NSTextAlignment = .center
22
+ var customWidth: CGFloat = 0
23
+ var ellipsis: Bool = false
24
+
25
+
26
+ override func drawText(in rect: CGRect) {
27
+ let shadowOffset = self.shadowOffset
28
+ let textColor = self.textColor
29
+
30
+ self.lineBreakMode = ellipsis ? .byTruncatingTail : .byWordWrapping
31
+
32
+ let adjustedRect = rect.inset(by: textInsets)
33
+
34
+ let context = UIGraphicsGetCurrentContext()
35
+ context?.setLineWidth(outlineWidth)
36
+ context?.setLineJoin(.round)
37
+ context?.setTextDrawingMode(.stroke)
38
+ self.textAlignment = align
39
+ self.textColor = outlineColor
40
+
41
+ super.drawText(in: adjustedRect)
42
+
43
+ context?.setTextDrawingMode(.fill)
44
+ self.textColor = textColor
45
+ self.shadowOffset = CGSize(width: 0, height: 0)
46
+ super.drawText(in: adjustedRect)
47
+
48
+ self.shadowOffset = shadowOffset
49
+ }
50
+
51
+ override var intrinsicContentSize: CGSize {
52
+ var contentSize = super.intrinsicContentSize
53
+ if customWidth > 0 {
54
+ contentSize.width = customWidth
55
+ } else {
56
+ contentSize.width += outlineWidth
57
+ }
58
+
59
+ contentSize.height += outlineWidth
60
+ return contentSize
61
+ }
62
+ }
package/lib/index.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ import React from "react";
2
+ import { type MatiksStrokeTextProps } from "./specs/Stroke.nitro";
3
+ interface StrokeTextViewProps extends MatiksStrokeTextProps {
4
+ styles?: {
5
+ width?: number;
6
+ height?: number;
7
+ };
8
+ }
9
+ declare const _default: React.MemoExoticComponent<({ styles, ...props }: StrokeTextViewProps) => React.JSX.Element>;
10
+ export default _default;