Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ import android.graphics.Paint
import android.text.Layout
import android.text.style.LeadingMarginSpan
import com.swmansion.reactnativerichtexteditor.spans.interfaces.EditorParagraphSpan
import com.swmansion.reactnativerichtexteditor.spans.interfaces.EditorZeroWidthSpaceSpan
import com.swmansion.reactnativerichtexteditor.styles.RichTextStyle

// https://android.googlesource.com/platform/frameworks/base/+/refs/heads/main/core/java/android/text/style/QuoteSpan.java
class EditorBlockQuoteSpan(private val richTextStyle: RichTextStyle) : LeadingMarginSpan, EditorParagraphSpan {
class EditorBlockQuoteSpan(private val richTextStyle: RichTextStyle) : LeadingMarginSpan, EditorParagraphSpan, EditorZeroWidthSpaceSpan {
override fun getLeadingMargin(p0: Boolean): Int {
return richTextStyle.blockquoteStripeWidth + richTextStyle.blockquoteGapWidth
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ import android.text.TextPaint
import android.text.style.CharacterStyle
import android.text.style.LineBackgroundSpan
import com.swmansion.reactnativerichtexteditor.spans.interfaces.EditorParagraphSpan
import com.swmansion.reactnativerichtexteditor.spans.interfaces.EditorZeroWidthSpaceSpan
import com.swmansion.reactnativerichtexteditor.styles.RichTextStyle

class EditorCodeBlockSpan(private val richTextStyle: RichTextStyle) : CharacterStyle(), LineBackgroundSpan, EditorParagraphSpan {
class EditorCodeBlockSpan(private val richTextStyle: RichTextStyle) : CharacterStyle(), LineBackgroundSpan, EditorParagraphSpan, EditorZeroWidthSpaceSpan {
override fun updateDrawState(paint: TextPaint?) {
paint?.typeface = Typeface.MONOSPACE
paint?.color = richTextStyle.codeBlockColor
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ package com.swmansion.reactnativerichtexteditor.spans
import android.text.style.AbsoluteSizeSpan
import com.swmansion.reactnativerichtexteditor.spans.interfaces.EditorHeadingSpan
import com.swmansion.reactnativerichtexteditor.spans.interfaces.EditorInlineSpan
import com.swmansion.reactnativerichtexteditor.spans.interfaces.EditorZeroWidthSpaceSpan
import com.swmansion.reactnativerichtexteditor.styles.RichTextStyle

// Heading spans inherit from EditorInlineSpan because they can be nested inside code block and block quote
class EditorH1Span(style: RichTextStyle) : AbsoluteSizeSpan(style.h1FontSize), EditorHeadingSpan, EditorInlineSpan {
class EditorH1Span(style: RichTextStyle) : AbsoluteSizeSpan(style.h1FontSize), EditorHeadingSpan, EditorInlineSpan, EditorZeroWidthSpaceSpan {
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ package com.swmansion.reactnativerichtexteditor.spans
import android.text.style.AbsoluteSizeSpan
import com.swmansion.reactnativerichtexteditor.spans.interfaces.EditorHeadingSpan
import com.swmansion.reactnativerichtexteditor.spans.interfaces.EditorInlineSpan
import com.swmansion.reactnativerichtexteditor.spans.interfaces.EditorZeroWidthSpaceSpan
import com.swmansion.reactnativerichtexteditor.styles.RichTextStyle

// Heading spans inherit from EditorInlineSpan because they can be nested inside code block and block quote
class EditorH2Span(richTextStyle: RichTextStyle) : AbsoluteSizeSpan(richTextStyle.h2FontSize), EditorHeadingSpan, EditorInlineSpan {
class EditorH2Span(richTextStyle: RichTextStyle) : AbsoluteSizeSpan(richTextStyle.h2FontSize), EditorHeadingSpan, EditorInlineSpan, EditorZeroWidthSpaceSpan {
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ package com.swmansion.reactnativerichtexteditor.spans
import android.text.style.AbsoluteSizeSpan
import com.swmansion.reactnativerichtexteditor.spans.interfaces.EditorHeadingSpan
import com.swmansion.reactnativerichtexteditor.spans.interfaces.EditorInlineSpan
import com.swmansion.reactnativerichtexteditor.spans.interfaces.EditorZeroWidthSpaceSpan
import com.swmansion.reactnativerichtexteditor.styles.RichTextStyle

// Heading spans inherit from EditorInlineSpan because they can be nested inside code block and block quote
class EditorH3Span(richTextStyle: RichTextStyle) : AbsoluteSizeSpan(richTextStyle.h3FontSize), EditorHeadingSpan, EditorInlineSpan {
class EditorH3Span(richTextStyle: RichTextStyle) : AbsoluteSizeSpan(richTextStyle.h3FontSize), EditorHeadingSpan, EditorInlineSpan, EditorZeroWidthSpaceSpan {
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import android.graphics.Paint
import android.text.Layout
import android.text.style.LeadingMarginSpan
import com.swmansion.reactnativerichtexteditor.spans.interfaces.EditorInlineSpan
import com.swmansion.reactnativerichtexteditor.spans.interfaces.EditorZeroWidthSpaceSpan
import com.swmansion.reactnativerichtexteditor.styles.RichTextStyle

class EditorOrderedListSpan(private var index: Int, private val richTextStyle: RichTextStyle) : LeadingMarginSpan, EditorInlineSpan {
class EditorOrderedListSpan(private var index: Int, private val richTextStyle: RichTextStyle) : LeadingMarginSpan, EditorInlineSpan, EditorZeroWidthSpaceSpan {
override fun getLeadingMargin(first: Boolean): Int {
return richTextStyle.olMarginLeft + richTextStyle.olGapWidth
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ import android.text.Layout
import android.text.Spanned
import android.text.style.LeadingMarginSpan
import com.swmansion.reactnativerichtexteditor.spans.interfaces.EditorInlineSpan
import com.swmansion.reactnativerichtexteditor.spans.interfaces.EditorZeroWidthSpaceSpan
import com.swmansion.reactnativerichtexteditor.styles.RichTextStyle

// https://android.googlesource.com/platform/frameworks/base/+/refs/heads/main/core/java/android/text/style/BulletSpan.java
class EditorUnorderedListSpan(private val richTextStyle: RichTextStyle) : LeadingMarginSpan, EditorInlineSpan {
class EditorUnorderedListSpan(private val richTextStyle: RichTextStyle) : LeadingMarginSpan, EditorInlineSpan, EditorZeroWidthSpaceSpan {
override fun getLeadingMargin(p0: Boolean): Int {
return richTextStyle.ulBulletSize + richTextStyle.ulGapWidth + richTextStyle.ulMarginLeft
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.swmansion.reactnativerichtexteditor.spans.interfaces

// Spans that are prefixed with zero-width space (\u200B) to ensure that they are visible even when empty.
interface EditorZeroWidthSpaceSpan {
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import com.swmansion.reactnativerichtexteditor.spans.EditorUnorderedListSpan;
import com.swmansion.reactnativerichtexteditor.spans.interfaces.EditorParagraphSpan;
import com.swmansion.reactnativerichtexteditor.spans.interfaces.EditorInlineSpan;
import com.swmansion.reactnativerichtexteditor.spans.interfaces.EditorZeroWidthSpaceSpan;
import com.swmansion.reactnativerichtexteditor.styles.RichTextStyle;

import org.ccil.cowan.tagsoup.HTMLSchema;
Expand Down Expand Up @@ -522,7 +523,10 @@ private static void withinStyle(StringBuilder out, CharSequence text,
int start, int end) {
for (int i = start; i < end; i++) {
char c = text.charAt(i);
if (c == '<') {
if (c == '\u200B') {
// Do not output zero-width space characters.
continue;
} else if (c == '<') {
out.append("&lt;");
} else if (c == '>') {
out.append("&gt;");
Expand Down Expand Up @@ -657,6 +661,17 @@ public Spanned convert() {
mSpannableStringBuilder.setSpan(obj[i], start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}

// Assign zero-width space character to the proper spans.
EditorZeroWidthSpaceSpan[] zeroWidthSpaceSpans = mSpannableStringBuilder.getSpans(0, mSpannableStringBuilder.length(), EditorZeroWidthSpaceSpan.class);
for (EditorZeroWidthSpaceSpan zeroWidthSpaceSpan : zeroWidthSpaceSpans) {
int start = mSpannableStringBuilder.getSpanStart(zeroWidthSpaceSpan);
int end = mSpannableStringBuilder.getSpanEnd(zeroWidthSpaceSpan);
mSpannableStringBuilder.insert(start, "\u200B");
mSpannableStringBuilder.removeSpan(zeroWidthSpaceSpan);
mSpannableStringBuilder.setSpan(zeroWidthSpaceSpan, start, end + 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}

return mSpannableStringBuilder;
}

Expand Down
Loading