Skip to content

Commit

Permalink
Fix Crash when setting maxLines in Text
Browse files Browse the repository at this point in the history
  • Loading branch information
MohamedRejeb committed Mar 30, 2024
1 parent d88ab60 commit 468d09a
Show file tree
Hide file tree
Showing 7 changed files with 81 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,10 @@ package com.mohamedrejeb.richeditor.annotation
" the future.",
level = RequiresOptIn.Level.WARNING
)
annotation class ExperimentalRichTextApi()
@Target(
AnnotationTarget.CLASS,
AnnotationTarget.FUNCTION,
AnnotationTarget.PROPERTY
)
@Retention(AnnotationRetention.BINARY)
annotation class ExperimentalRichTextApi
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.mohamedrejeb.richeditor.annotation

@RequiresOptIn(
level = RequiresOptIn.Level.ERROR,
message = "This is internal API that may change frequently and without warning."
)
@Target(
AnnotationTarget.CLASS,
AnnotationTarget.FUNCTION,
AnnotationTarget.PROPERTY
)
@Retention(AnnotationRetention.BINARY)
annotation class InternalRichTextApi
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ import androidx.compose.ui.text.TextLayoutResult
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.sp
import com.mohamedrejeb.richeditor.annotation.ExperimentalRichTextApi
import com.mohamedrejeb.richeditor.utils.fastForEachIndexed
import com.mohamedrejeb.richeditor.utils.getBoundingBoxes

@ExperimentalRichTextApi
interface RichSpanStyle {
val spanStyle: (RichTextConfig) -> SpanStyle

Expand Down Expand Up @@ -48,7 +50,7 @@ interface RichSpanStyle {
textRange: TextRange,
richTextConfig: RichTextConfig,
topPadding: Float,
startPadding: Float
startPadding: Float,
) = Unit

override val acceptNewTextInTheEdges: Boolean =
Expand Down Expand Up @@ -157,7 +159,7 @@ interface RichSpanStyle {
textRange: TextRange,
richTextConfig: RichTextConfig,
topPadding: Float,
startPadding: Float
startPadding: Float,
) = Unit

override val acceptNewTextInTheEdges: Boolean =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ package com.mohamedrejeb.richeditor.ui
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.text.TextRange
import com.mohamedrejeb.richeditor.annotation.ExperimentalRichTextApi
import com.mohamedrejeb.richeditor.model.RichSpanStyle
import com.mohamedrejeb.richeditor.model.RichTextState
import com.mohamedrejeb.richeditor.utils.fastForEach

@OptIn(ExperimentalRichTextApi::class)
internal fun Modifier.drawRichSpanStyle(
richTextState: RichTextState,
topPadding: Float = 0f,
Expand Down Expand Up @@ -42,9 +44,9 @@ internal fun Modifier.drawRichSpanStyle(
drawCustomStyle(
layoutResult = textLayoutResult,
textRange = textRange,
topPadding = topPadding,
startPadding = startPadding,
richTextConfig = richTextState.richTextConfig,
topPadding = topPadding,
startPadding = startPadding
)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package com.mohamedrejeb.richeditor.utils

import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.text.TextLayoutResult
import androidx.compose.ui.text.style.ResolvedTextDirection
import com.mohamedrejeb.richeditor.annotation.ExperimentalRichTextApi
import com.mohamedrejeb.richeditor.annotation.InternalRichTextApi
import kotlin.math.max
import kotlin.math.min

Expand All @@ -19,24 +22,42 @@ import kotlin.math.min
* @param flattenForFullParagraphs whether to return bounds for entire paragraphs instead of separate lines.
* @return the list of bounds for the given range.
*/
@ExperimentalRichTextApi
fun TextLayoutResult.getBoundingBoxes(
startOffset: Int,
endOffset: Int,
flattenForFullParagraphs: Boolean = false
): List<Rect> {
if (multiParagraph.lineCount == 0)
return emptyList()

val lastLinePosition =
Offset(
x = multiParagraph.getLineRight(multiParagraph.lineCount - 1),
y = multiParagraph.getLineTop(multiParagraph.lineCount - 1)
)

val lastOffset = multiParagraph.getOffsetForPosition(lastLinePosition)

if (startOffset >= lastOffset)
return emptyList()

if (startOffset == endOffset)
return emptyList()

if (startOffset < 0 || endOffset > layoutInput.text.length)
if (startOffset < 0 || endOffset < 0 || endOffset > layoutInput.text.length)
return emptyList()

val startLineNum = getLineForOffset(min(startOffset, endOffset))
val endLineNum = getLineForOffset(max(startOffset, endOffset))
val start = min(startOffset, endOffset)
val end = min(max(start, endOffset), lastOffset)

val startLineNum = getLineForOffset(min(start, end))
val endLineNum = getLineForOffset(max(start, end))

if (flattenForFullParagraphs) {
val isFullParagraph = (startLineNum != endLineNum)
&& getLineStart(startLineNum) == startOffset
&& multiParagraph.getLineEnd(endLineNum, visibleEnd = true) == endOffset
&& getLineStart(startLineNum) == start
&& multiParagraph.getLineEnd(endLineNum, visibleEnd = true) == end

if (isFullParagraph) {
return listOf(
Expand All @@ -53,22 +74,36 @@ fun TextLayoutResult.getBoundingBoxes(
// Compose UI does not offer any API for reading paragraph direction for an entire line.
// So this code assumes that all paragraphs in the text will have the same direction.
// It also assumes that this paragraph does not contain bi-directional text.
val isLtr = multiParagraph.getParagraphDirection(offset = layoutInput.text.lastIndex) == ResolvedTextDirection.Ltr
val isLtr = multiParagraph.getParagraphDirection(offset = start) == ResolvedTextDirection.Ltr

return fastMapRange(startLineNum, endLineNum) { lineNum ->
val left =
if (lineNum == startLineNum)
getHorizontalPosition(
offset = start,
usePrimaryDirection = isLtr
)
else
getLineLeft(
lineIndex = lineNum
)

val right =
if (lineNum == endLineNum)
getHorizontalPosition(
offset = end,
usePrimaryDirection = isLtr
)
else
getLineRight(
lineIndex = lineNum
)

Rect(
top = getLineTop(lineNum),
bottom = getLineBottom(lineNum),
left = if (lineNum == startLineNum) {
getHorizontalPosition(startOffset, usePrimaryDirection = isLtr)
} else {
getLineLeft(lineNum)
},
right = if (lineNum == endLineNum) {
getHorizontalPosition(endOffset, usePrimaryDirection = isLtr)
} else {
getLineRight(lineNum)
}
left = left,
right = right,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import androidx.compose.ui.unit.dp
import com.mohamedrejeb.richeditor.model.rememberRichTextState
import com.mohamedrejeb.richeditor.ui.material3.RichText

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun HtmlToRichText(
html: TextFieldValue,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextLayoutResult
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.unit.dp
import com.mohamedrejeb.richeditor.annotation.ExperimentalRichTextApi
import com.mohamedrejeb.richeditor.model.RichSpanStyle
import com.mohamedrejeb.richeditor.model.RichTextConfig
import com.mohamedrejeb.richeditor.utils.fastForEachIndexed
import com.mohamedrejeb.richeditor.utils.getBoundingBoxes

@OptIn(ExperimentalRichTextApi::class)
object SpellCheck: RichSpanStyle {
override val spanStyle: (RichTextConfig) -> SpanStyle = {
SpanStyle()
Expand Down

0 comments on commit 468d09a

Please sign in to comment.