Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions .maestro/enrichedInput/flows/dynamic_ol_markers.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
appId: swmansion.enriched.example
---
# Validates that ordered list margins dynamically adjust when transitioning
# between single-digit (9) and double-digit (10) indexes.
- launchApp

- tapOn:
id: 'toggle-screen-button'

- runFlow:
file: '../subflows/set_editor_value.yaml'
env:
VALUE: >
<html>
<ol>
<li>A</li>
<li>B</li>
<li>C</li>
<li>D</li>
<li>E</li>
<li>F</li>
<li>G</li>
<li>H</li>
<li>I</li>
</ol>
</html>

- tapOn:
id: 'size-max-button'

- runFlow:
file: '../subflows/capture_or_assert_screenshot.yaml'
env:
SCREENSHOT_NAME: 'dynamic_ol_markers_before'

- tapOn:
id: "editor-input"
point: "50%, 95%"
- pressKey: Enter
- inputText: "J"

- runFlow:
file: '../subflows/capture_or_assert_screenshot.yaml'
env:
SCREENSHOT_NAME: 'dynamic_ol_markers_after'

- tapOn:
id: "editor-input"
point: "50%, 95%"
- pressKey: Backspace
- pressKey: Backspace

- runFlow:
file: '../subflows/capture_or_assert_screenshot.yaml'
env:
SCREENSHOT_NAME: 'dynamic_ol_markers_revert'
Binary file modified .maestro/enrichedInput/screenshots/android/checkbox_toggle.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified .maestro/enrichedInput/screenshots/android/font_scaling.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified .maestro/enrichedInput/screenshots/android/inline_styles.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified .maestro/enrichedInput/screenshots/android/link_not_extended.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified .maestro/enrichedInput/screenshots/android/links_visual_auto.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified .maestro/enrichedInput/screenshots/android/links_visual_manual.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified .maestro/enrichedInput/screenshots/ios/empty_element_parsing.png
Binary file modified .maestro/enrichedInput/screenshots/ios/empty_lists_parsing.png
Binary file modified .maestro/enrichedInput/screenshots/ios/paragraph_styles_lists.png
41 changes: 41 additions & 0 deletions .maestro/enrichedText/flows/dynamic_ol_markers.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
appId: swmansion.enriched.example
---
# Validates that ordered list margins dynamically adjust when transitioning
# between single-digit (9) and double-digit (10) indexes.
- launchApp

- tapOn:
id: 'toggle-screen-button'

- tapOn:
id: 'toggle-enriched-text-screen-button'

- runFlow:
file: '../subflows/set_enriched_text_value.yaml'
env:
VALUE: >
<html>
<ol>
<li>A</li>
<li>B</li>
<li>C</li>
</ol>
<br>
<ol>
<li>A</li>
<li>B</li>
<li>C</li>
<li>D</li>
<li>E</li>
<li>F</li>
<li>G</li>
<li>H</li>
<li>I</li>
<li>J</li>
</ol>
</html>

- runFlow:
file: '../subflows/capture_or_assert_screenshot.yaml'
env:
SCREENSHOT_NAME: 'dynamic_ol_markers'
Binary file modified .maestro/enrichedText/screenshots/android/alignment_visual.png
Binary file modified .maestro/enrichedText/screenshots/android/font_scaling.png
Binary file modified .maestro/enrichedText/screenshots/ios/alignment_visual.png
Binary file modified .maestro/enrichedText/screenshots/ios/font_scaling.png
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.swmansion.enriched.common

import android.graphics.Paint
import android.text.Spanned
import com.swmansion.enriched.common.spans.EnrichedOrderedListSpan

// Recomputes the shared marker column width for every ordered-list item.
// Returns true when any item's column width changed, so callers can force a relayout.
fun updateOrderedListColumnMargins(
text: Spanned,
paint: Paint,
): Boolean {
val spans = text.getSpans(0, text.length, EnrichedOrderedListSpan::class.java)
val sortedSpans = spans.sortedBy { text.getSpanStart(it) }

var changed = false
var previousIndex = 0
var highestIndex = 0
for (span in sortedSpans.reversed()) {
val currentIndex = span.index
if (currentIndex > previousIndex) {
highestIndex = currentIndex
}
if (span.updateColumnMargin(paint, highestIndex)) changed = true

previousIndex = currentIndex
}

return changed
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,31 @@ import android.text.style.LeadingMarginSpan
import android.text.style.MetricAffectingSpan
import com.swmansion.enriched.common.EnrichedStyle
import com.swmansion.enriched.common.spans.interfaces.EnrichedParagraphSpan
import kotlin.math.ceil
import kotlin.math.max

open class EnrichedOrderedListSpan(
var index: Int,
private val enrichedStyle: EnrichedStyle,
) : MetricAffectingSpan(),
LeadingMarginSpan,
EnrichedParagraphSpan {
var columnMargin = enrichedStyle.olMarginLeft

// Computes the marker column width from the widest marker in the list ("<highestIndex>.").
// Returns true when the column width actually changed, so callers can force a relayout.
fun updateColumnMargin(
paint: Paint,
highestIndex: Int,
): Boolean {
val highestIndexText = "$highestIndex."
val highestIndexWidth = ceil(paint.measureText(highestIndexText)).toInt()
val newColumnMargin = max(enrichedStyle.olMarginLeft, highestIndexWidth)
if (newColumnMargin == columnMargin) return false
columnMargin = newColumnMargin
return true
}
Comment on lines +26 to +36

override fun updateMeasureState(p0: TextPaint) {
// Do nothing, but inform layout that this span affects text metrics
}
Expand All @@ -25,7 +43,7 @@ open class EnrichedOrderedListSpan(
// Do nothing, but inform layout that this span affects text metrics
}

override fun getLeadingMargin(first: Boolean): Int = enrichedStyle.olMarginLeft + enrichedStyle.olGapWidth
override fun getLeadingMargin(first: Boolean): Int = columnMargin + enrichedStyle.olGapWidth

override fun drawLeadingMargin(
canvas: Canvas,
Expand All @@ -46,7 +64,7 @@ open class EnrichedOrderedListSpan(
val width = paint.measureText(text)

val yPosition = baseline.toFloat()
val xPosition = (enrichedStyle.olMarginLeft + x - width / 2) * dir
val xPosition = (columnMargin + x - width) * dir

val originalColor = paint.color
val originalTypeface = paint.typeface
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ data class EnrichedTextStyle(
blockquoteStripeWidth = parseFloat(blockquote, "borderWidth", allowFontScaling).toInt(),
blockquoteGapWidth = parseFloat(blockquote, "gapWidth", allowFontScaling).toInt(),
olGapWidth = parseFloat(orderedList, "gapWidth", allowFontScaling).toInt(),
olMarginLeft = calculateOlMarginLeft(fontSize, parseFloat(orderedList, "marginLeft", allowFontScaling).toInt()),
olMarginLeft = parseFloat(orderedList, "marginLeft", allowFontScaling).toInt(),
olMarkerFontWeight = parseOptionalFontWeight(orderedList, "markerFontWeight"),
olMarkerColor = parseOptionalColor(context, orderedList, "markerColor"),
ulGapWidth = parseFloat(unorderedList, "gapWidth", allowFontScaling).toInt(),
Expand Down Expand Up @@ -169,14 +169,6 @@ data class EnrichedTextStyle(
return parseFontWeight(weight)
}

private fun calculateOlMarginLeft(
fontSize: Int,
userMargin: Int,
): Int {
val leadMargin = fontSize / 2
return leadMargin + userMargin
}

private fun parseMentionsStyle(
context: ReactContext,
map: ReadableMap?,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import com.swmansion.enriched.common.EnrichedSpanFlags
import com.swmansion.enriched.common.GumboNormalizer
import com.swmansion.enriched.common.parser.EnrichedParser
import com.swmansion.enriched.common.pixelFromSpOrDp
import com.swmansion.enriched.common.updateOrderedListColumnMargins
import com.swmansion.enriched.text.spans.EnrichedTextImageSpan
import com.swmansion.enriched.text.spans.interfaces.EnrichedTextClickableSpan
import com.swmansion.enriched.text.spans.interfaces.EnrichedTextSpan
Expand Down Expand Up @@ -179,6 +180,7 @@ class EnrichedTextView : AppCompatTextView {
val parsed = parseText(text, style)
if (parsed != null) {
parsedText = parsed
(parsed as? Spanned)?.let { updateOrderedListColumnMargins(it, paint) }
setText(parsed, BufferType.NORMAL)
observeAsyncImages()
} else {
Expand Down Expand Up @@ -283,6 +285,7 @@ class EnrichedTextView : AppCompatTextView {
}

if (modified) {
updateOrderedListColumnMargins(spannable, paint)
this.text = spannable
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import android.text.Editable
import android.text.InputType
import android.text.Spannable
import android.text.SpannableString
import android.text.Spanned
import android.util.AttributeSet
import android.util.Log
import android.util.Patterns
Expand Down Expand Up @@ -43,6 +44,7 @@ import com.swmansion.enriched.common.EnrichedSpanFlags
import com.swmansion.enriched.common.GumboNormalizer
import com.swmansion.enriched.common.parser.EnrichedParser
import com.swmansion.enriched.common.pixelFromSpOrDp
import com.swmansion.enriched.common.updateOrderedListColumnMargins
import com.swmansion.enriched.textinput.events.MentionHandler
import com.swmansion.enriched.textinput.events.OnContextMenuItemPressEvent
import com.swmansion.enriched.textinput.events.OnInputBlurEvent
Expand Down Expand Up @@ -448,6 +450,9 @@ class EnrichedTextInputView :

runAsATransaction {
val newText = if (shouldParseHtml) parseText(value) else value
// Compute ordered-list marker column widths before setText so the first layout reserves
// the correct leading margin (getLeadingMargin runs during layout, before drawing).
(newText as? Spanned)?.let { updateOrderedListColumnMargins(it, paint) }
setText(newText)
applyLineSpacing()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,7 @@ class HtmlStyle : EnrichedStyle {
blockquoteStripeWidth = parseFloat(blockquoteStyle, "borderWidth").toInt()

val olStyle = style.getMap("ol")
val userDefinedMarginLeft = parseFloat(olStyle, "marginLeft").toInt()
val calculatedMarginLeft = calculateOlMarginLeft(view, userDefinedMarginLeft)
olMarginLeft = calculatedMarginLeft
olMarginLeft = parseFloat(olStyle, "marginLeft").toInt()
olGapWidth = parseFloat(olStyle, "gapWidth").toInt()
olMarkerColor = parseOptionalColor(olStyle, "markerColor")
olMarkerFontWeight = parseOptionalFontWeight(olStyle, "markerFontWeight")
Expand Down Expand Up @@ -213,16 +211,6 @@ class HtmlStyle : EnrichedStyle {
throw Error("Specified textDecorationLine value is not supported: $underline. Supported values are 'underline' and 'none'.")
}

private fun calculateOlMarginLeft(
view: EnrichedTextInputView?,
userMargin: Int,
): Int {
val fontSize = view?.fontSize?.toInt() ?: 0
val leadMargin = fontSize / 2

return leadMargin + userMargin
}

private fun ensureValueIsSet(
map: ReadableMap?,
key: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import android.text.Editable
import android.text.Spannable
import android.text.SpannableStringBuilder
import android.text.Spanned
import android.text.style.ParagraphStyle
import com.swmansion.enriched.common.EnrichedConstants
import com.swmansion.enriched.common.EnrichedSpanFlags
import com.swmansion.enriched.common.updateOrderedListColumnMargins
import com.swmansion.enriched.textinput.EnrichedTextInputView
import com.swmansion.enriched.textinput.spans.EnrichedInputCheckboxListSpan
import com.swmansion.enriched.textinput.spans.EnrichedInputOrderedListSpan
Expand Down Expand Up @@ -104,17 +106,36 @@ class ListStyles(
return true
}

fun updateOrderedListIndexes(
text: Spannable,
position: Int,
) {
val spans = text.getSpans(position + 1, text.length, EnrichedInputOrderedListSpan::class.java)
fun updateOrderedListIndexes(text: Spannable) {
val spans = text.getSpans(0, text.length, EnrichedInputOrderedListSpan::class.java)
val sortedSpans = spans.sortedBy { text.getSpanStart(it) }
for (span in sortedSpans) {
val spanStart = text.getSpanStart(span)
val index = getOrderedListIndex(text, spanStart)
span.setListIndex(index)
}

val marginsChanged = updateOrderedListColumnMargins(text, view.paint)

// Ordered list margins got updated, so we need to force a re-layout of that list.
// Uses the same empty ParagraphStyle trick as EnrichedSpanWatcher.updateNextLineLayout.
if (marginsChanged) {
forceOrderedListRelayout(text, sortedSpans)
}
}

private fun forceOrderedListRelayout(
text: Spannable,
sortedSpans: List<EnrichedInputOrderedListSpan>,
) {
if (sortedSpans.isEmpty()) return

class EmptySpan : ParagraphStyle

val start = text.getSpanStart(sortedSpans.first())
val end = text.getSpanEnd(sortedSpans.last())
val (safeStart, safeEnd) = text.getSafeSpanBoundaries(start, end)
text.setSpan(EmptySpan(), safeStart, safeEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
Comment on lines +133 to +138
}

private fun toggleStyle(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class EnrichedSpanWatcher(
end: Int,
) {
updateNextLineLayout(what, text, end)
updateUnorderedListSpans(what, text, end)
updateUnorderedListSpans(what, text)
emitEvent(text, what)
}

Expand All @@ -38,7 +38,7 @@ class EnrichedSpanWatcher(
end: Int,
) {
updateNextLineLayout(what, text, end)
updateUnorderedListSpans(what, text, end)
updateUnorderedListSpans(what, text)
emitEvent(text, what)
}

Expand All @@ -56,10 +56,9 @@ class EnrichedSpanWatcher(
private fun updateUnorderedListSpans(
what: Any,
text: Spannable,
end: Int,
) {
if (what is EnrichedInputOrderedListSpan) {
view.listStyles?.updateOrderedListIndexes(text, end)
view.listStyles?.updateOrderedListIndexes(text)
}
}

Expand Down
2 changes: 1 addition & 1 deletion apps/example/src/constants/editorConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ export const htmlStyle = {
},
ol: {
gapWidth: 16,
marginLeft: 24,
marginLeft: 16,
markerColor: 'navy',
markerFontWeight: 'bold',
},
Expand Down
2 changes: 1 addition & 1 deletion ios/extensions/LayoutManagerExtension.mm
Original file line number Diff line number Diff line change
Expand Up @@ -448,7 +448,7 @@ - (void)drawDecimal:(id<EnrichedViewHost>)host
indent:(CGFloat)indent {
CGFloat gapWidth = [host.config orderedListGapWidth];
CGSize markerSize = [marker sizeWithAttributes:markerAttributes];
CGFloat markerX = origin.x + indent - gapWidth - markerSize.width / 2;
CGFloat markerX = origin.x + indent - gapWidth - markerSize.width;
UIFont *markerFont = markerAttributes[NSFontAttributeName];

// drawAtPoint draws from the top-left bounding box of the string.
Expand Down
Loading
Loading