react-native-highlight-text-view 0.1.30 → 0.1.31

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.
@@ -4,6 +4,7 @@ import android.content.Context
4
4
  import android.graphics.Canvas
5
5
  import android.graphics.Color
6
6
  import android.graphics.Paint
7
+ import android.graphics.Path
7
8
  import android.graphics.RectF
8
9
  import android.graphics.Typeface
9
10
  import android.text.Editable
@@ -42,6 +43,7 @@ class HighlightTextView : AppCompatEditText {
42
43
 
43
44
  // Line height control
44
45
  private var customLineHeight: Float = 0f
46
+ private var customLineSpacing: Float = 0f
45
47
 
46
48
  // Font + alignment state
47
49
  private var currentFontFamily: String? = null
@@ -58,6 +60,8 @@ class HighlightTextView : AppCompatEditText {
58
60
  style = Paint.Style.FILL
59
61
  }
60
62
  private val backgroundRect = RectF()
63
+ private val backgroundPath = Path()
64
+ private val radii = FloatArray(8)
61
65
 
62
66
  var onTextChangeListener: ((String) -> Unit)? = null
63
67
 
@@ -88,6 +92,7 @@ class HighlightTextView : AppCompatEditText {
88
92
  maxLines = Int.MAX_VALUE
89
93
  isSingleLine = false
90
94
  setHorizontallyScrolling(false)
95
+ includeFontPadding = false
91
96
 
92
97
  applyLineHeightAndSpacing()
93
98
 
@@ -148,6 +153,37 @@ class HighlightTextView : AppCompatEditText {
148
153
  val lineStart = layout.getLineStart(line)
149
154
  val lineEnd = layout.getLineEnd(line)
150
155
 
156
+ // Determine adjacency for selective rounding
157
+ val hasLeftNeighbor = if (i > 0) {
158
+ val prevCh = text[i - 1]
159
+ val sameLine = layout.getLineForOffset(i - 1) == line
160
+ sameLine && prevCh != '\n' && prevCh != '\t'
161
+ } else false
162
+
163
+ val hasRightNeighbor = if (i < length - 1) {
164
+ val nextCh = text[i + 1]
165
+ val sameLine = layout.getLineForOffset(i + 1) == line
166
+
167
+ if (!sameLine || nextCh == '\n' || nextCh == '\t') {
168
+ false
169
+ } else if (nextCh == ' ') {
170
+ // Lookahead: If next is space, check if any visible char follows on same line
171
+ var hasVisibleAfter = false
172
+ for (k in i + 2 until length) {
173
+ if (layout.getLineForOffset(k) != line) break
174
+ val c = text[k]
175
+ if (c == '\n' || c == '\t') break
176
+ if (c != ' ') {
177
+ hasVisibleAfter = true
178
+ break
179
+ }
180
+ }
181
+ hasVisibleAfter
182
+ } else {
183
+ true
184
+ }
185
+ } else false
186
+
151
187
  // Horizontal bounds based on layout positions
152
188
  val xStart = layout.getPrimaryHorizontal(i)
153
189
  val isLastCharInLine = i == lineEnd - 1
@@ -179,13 +215,97 @@ class HighlightTextView : AppCompatEditText {
179
215
  top -= charPaddingTop
180
216
  bottom += charPaddingBottom
181
217
 
218
+ if (customLineSpacing < 0f) {
219
+ val originalLineTop = layout.getLineTop(line).toFloat()
220
+ val originalLineBottom = layout.getLineBottom(line).toFloat()
221
+
222
+ if (line > 0 && top < originalLineTop) {
223
+ val prevLineBottom = layout.getLineBottom(line - 1).toFloat()
224
+ if (top < prevLineBottom) {
225
+ top = prevLineBottom
226
+ }
227
+ }
228
+
229
+ if (line < layout.lineCount - 1 && bottom > originalLineBottom) {
230
+ val nextLineTop = layout.getLineTop(line + 1).toFloat()
231
+ if (bottom > nextLineTop) {
232
+ bottom = nextLineTop
233
+ }
234
+ }
235
+ }
236
+
182
237
  if (right <= left || bottom <= top) continue
183
238
 
184
239
  backgroundRect.set(left, top, right, bottom)
185
- canvas.drawRoundRect(backgroundRect, radius, radius, backgroundPaint)
240
+
241
+ // Detect paragraph boundaries (empty lines above/below)
242
+ val isFirstLineOfParagraph = line == 0 || isLineEmpty(text, layout, line - 1)
243
+ val isLastLineOfParagraph = line == layout.lineCount - 1 || isLineEmpty(text, layout, line + 1)
244
+
245
+ var tl = 0f
246
+ var tr = 0f
247
+ var br = 0f
248
+ var bl = 0f
249
+
250
+ // Left Edge Logic
251
+ if (!hasLeftNeighbor) {
252
+ // Top-Left: Round if first line of paragraph
253
+ tl = if (isFirstLineOfParagraph) radius else 0f
254
+ // Bottom-Left: Round if last line of paragraph
255
+ bl = if (isLastLineOfParagraph) radius else 0f
256
+ }
257
+
258
+ // Right Edge Logic
259
+ if (!hasRightNeighbor) {
260
+ val currentLineWidth = layout.getLineMax(line)
261
+
262
+ // Top-Right
263
+ if (isFirstLineOfParagraph) {
264
+ tr = radius
265
+ } else {
266
+ val prevLineWidth = layout.getLineMax(line - 1)
267
+ // Round Top-Right if we stick out further than the line above
268
+ tr = if (currentLineWidth > prevLineWidth) radius else 0f
269
+ }
270
+
271
+ // Bottom-Right
272
+ if (isLastLineOfParagraph) {
273
+ br = radius
274
+ } else {
275
+ val nextLineWidth = layout.getLineMax(line + 1)
276
+ // Round Bottom-Right if we overhang the line below
277
+ br = if (currentLineWidth > nextLineWidth) radius else 0f
278
+ }
279
+ }
280
+
281
+ // Arrays: Top-Left x,y; Top-Right x,y; Bottom-Right x,y; Bottom-Left x,y
282
+ radii[0] = tl; radii[1] = tl
283
+ radii[2] = tr; radii[3] = tr
284
+ radii[4] = br; radii[5] = br
285
+ radii[6] = bl; radii[7] = bl
286
+
287
+ backgroundPath.reset()
288
+ backgroundPath.addRoundRect(backgroundRect, radii, Path.Direction.CW)
289
+ canvas.drawPath(backgroundPath, backgroundPaint)
186
290
  }
187
291
  }
188
292
 
293
+ private fun isLineEmpty(text: CharSequence, layout: android.text.Layout, line: Int): Boolean {
294
+ if (line < 0 || line >= layout.lineCount) return false
295
+
296
+ val lineStart = layout.getLineStart(line)
297
+ val lineEnd = layout.getLineEnd(line)
298
+
299
+ // Check if line contains only whitespace/newlines
300
+ for (i in lineStart until lineEnd) {
301
+ val ch = text[i]
302
+ if (ch != '\n' && ch != '\t' && ch != ' ') {
303
+ return false
304
+ }
305
+ }
306
+ return true
307
+ }
308
+
189
309
  // --- Public API used from the ViewManager ------------------------------------
190
310
 
191
311
  fun setCharacterBackgroundColor(color: Int) {
@@ -371,6 +491,13 @@ class HighlightTextView : AppCompatEditText {
371
491
  post { invalidate() }
372
492
  }
373
493
 
494
+ fun setCustomLineSpacing(spacing: Float) {
495
+ customLineSpacing = spacing
496
+ applyLineHeightAndSpacing()
497
+ requestLayout()
498
+ post { invalidate() }
499
+ }
500
+
374
501
  fun setLetterSpacingProp(points: Float) {
375
502
  letterSpacingPoints = points
376
503
  applyLetterSpacing()
@@ -415,15 +542,24 @@ class HighlightTextView : AppCompatEditText {
415
542
  // --- Layout helpers ----------------------------------------------------------
416
543
 
417
544
  private fun applyLineHeightAndSpacing() {
545
+ val metrics = resources.displayMetrics
546
+
418
547
  if (customLineHeight > 0f) {
419
548
  // customLineHeight comes from JS as "points"; convert to px using scaledDensity
420
- val metrics = resources.displayMetrics
421
549
  val desiredLineHeightPx = customLineHeight * metrics.scaledDensity
422
550
  val textHeightPx = textSize
423
551
  if (textHeightPx > 0f) {
424
552
  val multiplier = desiredLineHeightPx / textHeightPx
425
- setLineSpacing(0f, multiplier)
553
+ val extraSpacing = if (customLineSpacing != 0f) {
554
+ customLineSpacing * metrics.scaledDensity
555
+ } else {
556
+ 0f
557
+ }
558
+ setLineSpacing(extraSpacing, multiplier)
426
559
  }
560
+ } else if (customLineSpacing != 0f) {
561
+ val extraSpacing = customLineSpacing * metrics.scaledDensity
562
+ setLineSpacing(extraSpacing, 1.0f)
427
563
  } else {
428
564
  // Default: add extra spacing equal to vertical padding so backgrounds don't collide
429
565
  val extraSpacing = charPaddingTop + charPaddingBottom
@@ -183,6 +183,13 @@ class HighlightTextViewManager : SimpleViewManager<HighlightTextView>(),
183
183
  }
184
184
  }
185
185
 
186
+ @ReactProp(name = "lineSpacing")
187
+ override fun setLineSpacing(view: HighlightTextView?, value: String?) {
188
+ value?.toFloatOrNull()?.let { spacing ->
189
+ view?.setCustomLineSpacing(spacing)
190
+ }
191
+ }
192
+
186
193
  @ReactProp(name = "highlightBorderRadius")
187
194
  override fun setHighlightBorderRadius(view: HighlightTextView?, value: String?) {
188
195
  value?.toFloatOrNull()?.let { radius ->
@@ -50,6 +50,7 @@ export interface HighlightTextViewProps extends ViewProps {
50
50
  /** Additional space between characters, in layout points (matches React Native's letterSpacing). */
51
51
  letterSpacing?: string;
52
52
  lineHeight?: string;
53
+ lineSpacing?: string;
53
54
  highlightBorderRadius?: string;
54
55
  padding?: string;
55
56
  paddingLeft?: string;
@@ -32,6 +32,7 @@ export interface HighlightTextViewProps extends ViewProps {
32
32
  /** Additional space between characters, in layout points (matches React Native's letterSpacing). */
33
33
  letterSpacing?: string;
34
34
  lineHeight?: string;
35
+ lineSpacing?: string;
35
36
  highlightBorderRadius?: string;
36
37
  padding?: string;
37
38
  paddingLeft?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"HighlightTextViewNativeComponent.d.ts","sourceRoot":"","sources":["../../../src/HighlightTextViewNativeComponent.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAEtF,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,MAAM,aAAa,GACrB,MAAM,GACN,QAAQ,GACR,OAAO,GACP,SAAS,GACT,YAAY,GACZ,UAAU,GACV,KAAK,GACL,QAAQ,GACR,UAAU,GACV,YAAY,GACZ,WAAW,GACX,aAAa,GACb,eAAe,GACf,cAAc,CAAC;AAEnB,MAAM,WAAW,sBAAuB,SAAQ,SAAS;IACvD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,oGAAoG;IACpG,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,iFAAiF;IACjF,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,oFAAoF;IACpF,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,gFAAgF;IAChF,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,iFAAiF;IACjF,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,oBAAoB,CAAC,iBAAiB,CAAC,CAAC;CACpD;;AAED,wBAEE"}
1
+ {"version":3,"file":"HighlightTextViewNativeComponent.d.ts","sourceRoot":"","sources":["../../../src/HighlightTextViewNativeComponent.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAEtF,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,MAAM,aAAa,GACrB,MAAM,GACN,QAAQ,GACR,OAAO,GACP,SAAS,GACT,YAAY,GACZ,UAAU,GACV,KAAK,GACL,QAAQ,GACR,UAAU,GACV,YAAY,GACZ,WAAW,GACX,aAAa,GACb,eAAe,GACf,cAAc,CAAC;AAEnB,MAAM,WAAW,sBAAuB,SAAQ,SAAS;IACvD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,oGAAoG;IACpG,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,iFAAiF;IACjF,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,oFAAoF;IACpF,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,gFAAgF;IAChF,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,iFAAiF;IACjF,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,oBAAoB,CAAC,iBAAiB,CAAC,CAAC;CACpD;;AAED,wBAEE"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-highlight-text-view",
3
- "version": "0.1.30",
3
+ "version": "0.1.31",
4
4
  "description": "A native text input for React Native that supports inline text highlighting",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",
@@ -50,6 +50,7 @@ export interface HighlightTextViewProps extends ViewProps {
50
50
  /** Additional space between characters, in layout points (matches React Native's letterSpacing). */
51
51
  letterSpacing?: string;
52
52
  lineHeight?: string;
53
+ lineSpacing?: string;
53
54
  highlightBorderRadius?: string;
54
55
  padding?: string;
55
56
  paddingLeft?: string;