react-native-advanced-text 0.2.14 → 0.2.15

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.
@@ -3,9 +3,12 @@ package com.advancedtext
3
3
  import android.content.Context
4
4
  import android.graphics.Canvas
5
5
  import android.graphics.Color
6
+ import android.graphics.Paint
7
+ import android.graphics.Path
6
8
  import android.graphics.Point
7
9
  import android.graphics.RectF
8
10
  import android.os.Build
11
+ import android.text.Layout
9
12
  import android.text.SpannableString
10
13
  import android.text.Spannable
11
14
  import android.text.Spanned
@@ -58,6 +61,12 @@ class AdvancedTextView : TextView {
58
61
  // text. setText() would clear any selection the user is making.
59
62
  private var wordColorSpans: Array<ForegroundColorSpan?> = emptyArray()
60
63
 
64
+ // Reused on every draw for the highlight backgrounds.
65
+ private val highlightPaint = Paint(Paint.ANTI_ALIAS_FLAG)
66
+ private val rangePath = Path()
67
+ private val lineClipPath = Path()
68
+ private val rangeBounds = RectF()
69
+
61
70
  constructor(context: Context?) : super(context) { init() }
62
71
  constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) { init() }
63
72
  constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { init() }
@@ -382,87 +391,78 @@ class AdvancedTextView : TextView {
382
391
  private fun drawHighlightedWordBackgrounds(canvas: Canvas) {
383
392
  val currentLayout = layout ?: return
384
393
  val density = resources.displayMetrics.density
385
- val originalColor = paint.color
386
394
 
387
395
  highlightedWords.forEach { highlightedWord ->
388
396
  val wordPos = wordPositions.find { it.index == highlightedWord.index } ?: return@forEach
389
- val start = wordPos.start
390
- val end = wordPos.extendedEnd.coerceAtMost(currentText.length)
391
-
392
- if (start >= end) return@forEach
393
-
394
- val startLine = currentLayout.getLineForOffset(start)
395
- val safeEndOffset = (end - 1).coerceAtLeast(start)
396
- val endLine = currentLayout.getLineForOffset(safeEndOffset)
397
-
398
- paint.color = parseColor(highlightedWord.highlightColor)
399
-
400
- for (line in startLine..endLine) {
401
- val lineStartOffset = maxOf(start, currentLayout.getLineStart(line))
402
- val lineEndOffset = minOf(end, currentLayout.getLineEnd(line))
403
- if (lineStartOffset >= lineEndOffset) continue
404
-
405
- val left = currentLayout.getPrimaryHorizontal(lineStartOffset)
406
- val isLastWordOnLine = lineEndOffset >= currentLayout.getLineEnd(line)
407
- val right = if (isLastWordOnLine) {
408
- currentLayout.getLineRight(line)
409
- } else {
410
- currentLayout.getPrimaryHorizontal(lineEndOffset)
411
- }
412
- val top = currentLayout.getLineTop(line).toFloat()
413
- val bottom = currentLayout.getLineBottom(line).toFloat()
414
- val rect = RectF(
415
- totalPaddingLeft + left,
416
- totalPaddingTop + top,
417
- totalPaddingLeft + right,
418
- totalPaddingTop + bottom
419
- )
420
- val radius = highlightedWord.borderRadius * density
421
- canvas.drawRoundRect(rect, radius, radius, paint)
422
- }
397
+ drawRangeBackground(
398
+ canvas,
399
+ currentLayout,
400
+ wordPos.start,
401
+ wordPos.extendedEnd.coerceAtMost(currentText.length),
402
+ parseColor(highlightedWord.highlightColor),
403
+ highlightedWord.borderRadius * density
404
+ )
423
405
  }
424
406
 
425
407
  // Draw indicator background only in Highlight mode
426
408
  if (indicatorMode != "fill" && indicatorWordIndex >= 0 && indicatorColor.isNotEmpty()) {
427
409
  val wordPos = wordPositions.find { it.index == indicatorWordIndex }
428
410
  if (wordPos != null) {
429
- val start = wordPos.start
430
- val end = wordPos.end.coerceAtMost(currentText.length)
431
-
432
- if (start < end) {
433
- val startLine = currentLayout.getLineForOffset(start)
434
- val safeEndOffset = (end - 1).coerceAtLeast(start)
435
- val endLine = currentLayout.getLineForOffset(safeEndOffset)
436
-
437
- paint.color = parseColor(indicatorColor)
438
-
439
- for (line in startLine..endLine) {
440
- val lineStartOffset = maxOf(start, currentLayout.getLineStart(line))
441
- val lineEndOffset = minOf(end, currentLayout.getLineEnd(line))
442
- if (lineStartOffset >= lineEndOffset) continue
443
-
444
- val left = currentLayout.getPrimaryHorizontal(lineStartOffset)
445
- val isLastWordOnLine = lineEndOffset >= currentLayout.getLineEnd(line)
446
- val right = if (isLastWordOnLine) {
447
- currentLayout.getLineRight(line)
448
- } else {
449
- currentLayout.getPrimaryHorizontal(lineEndOffset)
450
- }
451
- val top = currentLayout.getLineTop(line).toFloat()
452
- val bottom = currentLayout.getLineBottom(line).toFloat()
453
- val rect = RectF(
454
- totalPaddingLeft + left,
455
- totalPaddingTop + top,
456
- totalPaddingLeft + right,
457
- totalPaddingTop + bottom
458
- )
459
- canvas.drawRoundRect(rect, 0f, 0f, paint)
460
- }
461
- }
411
+ drawRangeBackground(
412
+ canvas,
413
+ currentLayout,
414
+ wordPos.start,
415
+ wordPos.end.coerceAtMost(currentText.length),
416
+ parseColor(indicatorColor),
417
+ 0f
418
+ )
462
419
  }
463
420
  }
421
+ }
422
+
423
+ // Fills the background of text[start, end), one box per line. The edges
424
+ // come from Layout.getSelectionPath, which follows the text's direction:
425
+ // a right-to-left word (Arabic) starts at its right edge, so treating
426
+ // getPrimaryHorizontal(start) as the left edge drew boxes in the wrong place.
427
+ private fun drawRangeBackground(
428
+ canvas: Canvas,
429
+ layout: Layout,
430
+ start: Int,
431
+ end: Int,
432
+ color: Int,
433
+ radius: Float
434
+ ) {
435
+ if (start >= end) return
436
+ highlightPaint.color = color
437
+
438
+ val startLine = layout.getLineForOffset(start)
439
+ val endLine = layout.getLineForOffset(end - 1)
440
+ for (line in startLine..endLine) {
441
+ val segmentStart = maxOf(start, layout.getLineStart(line))
442
+ val segmentEnd = minOf(end, layout.getLineEnd(line))
443
+ if (segmentStart >= segmentEnd) continue
444
+
445
+ rangePath.reset()
446
+ layout.getSelectionPath(segmentStart, segmentEnd, rangePath)
447
+
448
+ // A range reaching the end of a line counts as running onto the
449
+ // next one, so the selection path fills out to the view's edge.
450
+ // Clip to this line's text to stop the box at the last word.
451
+ lineClipPath.reset()
452
+ lineClipPath.addRect(
453
+ layout.getLineLeft(line),
454
+ layout.getLineTop(line).toFloat(),
455
+ layout.getLineRight(line),
456
+ layout.getLineBottom(line).toFloat(),
457
+ Path.Direction.CW
458
+ )
459
+ rangePath.op(lineClipPath, Path.Op.INTERSECT)
460
+ rangePath.computeBounds(rangeBounds, true)
461
+ if (rangeBounds.isEmpty) continue
464
462
 
465
- paint.color = originalColor
463
+ rangeBounds.offset(totalPaddingLeft.toFloat(), totalPaddingTop.toFloat())
464
+ canvas.drawRoundRect(rangeBounds, radius, radius, highlightPaint)
465
+ }
466
466
  }
467
467
 
468
468
  private fun parseColor(colorString: String): Int {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-advanced-text",
3
- "version": "0.2.14",
3
+ "version": "0.2.15",
4
4
  "description": " Advanced text component for React Native with custom select options.",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",