Skip to content

Commit 47dc68a

Browse files
committed
feat: add heading styles
1 parent fa2109e commit 47dc68a

25 files changed

+525
-45
lines changed

android/src/main/java/com/swmansion/enriched/EnrichedTextInputView.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,9 @@ class EnrichedTextInputView : AppCompatEditText {
409409
EnrichedSpans.H1 -> paragraphStyles?.toggleStyle(EnrichedSpans.H1)
410410
EnrichedSpans.H2 -> paragraphStyles?.toggleStyle(EnrichedSpans.H2)
411411
EnrichedSpans.H3 -> paragraphStyles?.toggleStyle(EnrichedSpans.H3)
412+
EnrichedSpans.H4 -> paragraphStyles?.toggleStyle(EnrichedSpans.H4)
413+
EnrichedSpans.H5 -> paragraphStyles?.toggleStyle(EnrichedSpans.H5)
414+
EnrichedSpans.H6 -> paragraphStyles?.toggleStyle(EnrichedSpans.H6)
412415
EnrichedSpans.CODE_BLOCK -> paragraphStyles?.toggleStyle(EnrichedSpans.CODE_BLOCK)
413416
EnrichedSpans.BLOCK_QUOTE -> paragraphStyles?.toggleStyle(EnrichedSpans.BLOCK_QUOTE)
414417
EnrichedSpans.ORDERED_LIST -> listStyles?.toggleStyle(EnrichedSpans.ORDERED_LIST)
@@ -429,6 +432,9 @@ class EnrichedTextInputView : AppCompatEditText {
429432
EnrichedSpans.H1 -> paragraphStyles?.removeStyle(EnrichedSpans.H1, start, end)
430433
EnrichedSpans.H2 -> paragraphStyles?.removeStyle(EnrichedSpans.H2, start, end)
431434
EnrichedSpans.H3 -> paragraphStyles?.removeStyle(EnrichedSpans.H3, start, end)
435+
EnrichedSpans.H4 -> paragraphStyles?.removeStyle(EnrichedSpans.H4, start, end)
436+
EnrichedSpans.H5 -> paragraphStyles?.removeStyle(EnrichedSpans.H5, start, end)
437+
EnrichedSpans.H6 -> paragraphStyles?.removeStyle(EnrichedSpans.H6, start, end)
432438
EnrichedSpans.CODE_BLOCK -> paragraphStyles?.removeStyle(EnrichedSpans.CODE_BLOCK, start, end)
433439
EnrichedSpans.BLOCK_QUOTE -> paragraphStyles?.removeStyle(EnrichedSpans.BLOCK_QUOTE, start, end)
434440
EnrichedSpans.ORDERED_LIST -> listStyles?.removeStyle(EnrichedSpans.ORDERED_LIST, start, end)
@@ -452,6 +458,9 @@ class EnrichedTextInputView : AppCompatEditText {
452458
EnrichedSpans.H1 -> paragraphStyles?.getStyleRange()
453459
EnrichedSpans.H2 -> paragraphStyles?.getStyleRange()
454460
EnrichedSpans.H3 -> paragraphStyles?.getStyleRange()
461+
EnrichedSpans.H4 -> paragraphStyles?.getStyleRange()
462+
EnrichedSpans.H5 -> paragraphStyles?.getStyleRange()
463+
EnrichedSpans.H6 -> paragraphStyles?.getStyleRange()
455464
EnrichedSpans.CODE_BLOCK -> paragraphStyles?.getStyleRange()
456465
EnrichedSpans.BLOCK_QUOTE -> paragraphStyles?.getStyleRange()
457466
EnrichedSpans.ORDERED_LIST -> listStyles?.getStyleRange()

android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewManager.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,18 @@ class EnrichedTextInputViewManager : SimpleViewManager<EnrichedTextInputView>(),
235235
view?.verifyAndToggleStyle(EnrichedSpans.H3)
236236
}
237237

238+
override fun toggleH4(view: EnrichedTextInputView?) {
239+
view?.verifyAndToggleStyle(EnrichedSpans.H4)
240+
}
241+
242+
override fun toggleH5(view: EnrichedTextInputView?) {
243+
view?.verifyAndToggleStyle(EnrichedSpans.H5)
244+
}
245+
246+
override fun toggleH6(view: EnrichedTextInputView?) {
247+
view?.verifyAndToggleStyle(EnrichedSpans.H6)
248+
}
249+
238250
override fun toggleCodeBlock(view: EnrichedTextInputView?) {
239251
view?.verifyAndToggleStyle(EnrichedSpans.CODE_BLOCK)
240252
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.swmansion.enriched.spans
2+
3+
import android.graphics.Typeface
4+
import android.text.TextPaint
5+
import android.text.style.AbsoluteSizeSpan
6+
import com.swmansion.enriched.spans.interfaces.EnrichedHeadingSpan
7+
import com.swmansion.enriched.styles.HtmlStyle
8+
9+
class EnrichedH4Span(private val htmlStyle: HtmlStyle) : AbsoluteSizeSpan(htmlStyle.h4FontSize), EnrichedHeadingSpan {
10+
override fun updateDrawState(tp: TextPaint) {
11+
super.updateDrawState(tp)
12+
val bold = htmlStyle.h4Bold
13+
if (bold) {
14+
tp.typeface = Typeface.create(tp.typeface, Typeface.BOLD)
15+
}
16+
}
17+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.swmansion.enriched.spans
2+
3+
import android.graphics.Typeface
4+
import android.text.TextPaint
5+
import android.text.style.AbsoluteSizeSpan
6+
import com.swmansion.enriched.spans.interfaces.EnrichedHeadingSpan
7+
import com.swmansion.enriched.styles.HtmlStyle
8+
9+
class EnrichedH5Span(private val htmlStyle: HtmlStyle) : AbsoluteSizeSpan(htmlStyle.h5FontSize), EnrichedHeadingSpan {
10+
override fun updateDrawState(tp: TextPaint) {
11+
super.updateDrawState(tp)
12+
val bold = htmlStyle.h5Bold
13+
if (bold) {
14+
tp.typeface = Typeface.create(tp.typeface, Typeface.BOLD)
15+
}
16+
}
17+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.swmansion.enriched.spans
2+
3+
import android.graphics.Typeface
4+
import android.text.TextPaint
5+
import android.text.style.AbsoluteSizeSpan
6+
import com.swmansion.enriched.spans.interfaces.EnrichedHeadingSpan
7+
import com.swmansion.enriched.styles.HtmlStyle
8+
9+
class EnrichedH6Span(private val htmlStyle: HtmlStyle) : AbsoluteSizeSpan(htmlStyle.h6FontSize), EnrichedHeadingSpan {
10+
override fun updateDrawState(tp: TextPaint) {
11+
super.updateDrawState(tp)
12+
val bold = htmlStyle.h6Bold
13+
if (bold) {
14+
tp.typeface = Typeface.create(tp.typeface, Typeface.BOLD)
15+
}
16+
}
17+
}

android/src/main/java/com/swmansion/enriched/spans/EnrichedSpans.kt

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ object EnrichedSpans {
2525
const val H1 = "h1"
2626
const val H2 = "h2"
2727
const val H3 = "h3"
28+
const val H4 = "h4"
29+
const val H5 = "h5"
30+
const val H6 = "h6"
2831
const val BLOCK_QUOTE = "block_quote"
2932
const val CODE_BLOCK = "code_block"
3033

@@ -49,6 +52,9 @@ object EnrichedSpans {
4952
H1 to ParagraphSpanConfig(EnrichedH1Span::class.java, false),
5053
H2 to ParagraphSpanConfig(EnrichedH2Span::class.java, false),
5154
H3 to ParagraphSpanConfig(EnrichedH3Span::class.java, false),
55+
H4 to ParagraphSpanConfig(EnrichedH4Span::class.java, false),
56+
H5 to ParagraphSpanConfig(EnrichedH5Span::class.java, false),
57+
H6 to ParagraphSpanConfig(EnrichedH6Span::class.java, false),
5258
BLOCK_QUOTE to ParagraphSpanConfig(EnrichedBlockQuoteSpan::class.java, true),
5359
CODE_BLOCK to ParagraphSpanConfig(EnrichedCodeBlockSpan::class.java, true),
5460
)
@@ -71,6 +77,9 @@ object EnrichedSpans {
7177
if (htmlStyle.h1Bold) blockingStyles.add(H1)
7278
if (htmlStyle.h2Bold) blockingStyles.add(H2)
7379
if (htmlStyle.h3Bold) blockingStyles.add(H3)
80+
if (htmlStyle.h4Bold) blockingStyles.add(H4)
81+
if (htmlStyle.h5Bold) blockingStyles.add(H5)
82+
if (htmlStyle.h6Bold) blockingStyles.add(H6)
7483
StylesMergingConfig(blockingStyles = blockingStyles.toTypedArray())
7584
}
7685
ITALIC -> StylesMergingConfig(
@@ -87,31 +96,46 @@ object EnrichedSpans {
8796
blockingStyles = arrayOf(CODE_BLOCK)
8897
)
8998
H1 -> {
90-
val conflictingStyles = mutableListOf(H2, H3, ORDERED_LIST, UNORDERED_LIST, BLOCK_QUOTE, CODE_BLOCK)
99+
val conflictingStyles = mutableListOf(H2, H3, H4, H5, H6, ORDERED_LIST, UNORDERED_LIST, BLOCK_QUOTE, CODE_BLOCK)
91100
if (htmlStyle.h1Bold) conflictingStyles.add(BOLD)
92101
StylesMergingConfig(conflictingStyles = conflictingStyles.toTypedArray())
93102
}
94103
H2 -> {
95-
val conflictingStyles = mutableListOf(H1, H3, ORDERED_LIST, UNORDERED_LIST, BLOCK_QUOTE, CODE_BLOCK)
104+
val conflictingStyles = mutableListOf(H1, H3, H4, H5, H6, ORDERED_LIST, UNORDERED_LIST, BLOCK_QUOTE, CODE_BLOCK)
96105
if (htmlStyle.h2Bold) conflictingStyles.add(BOLD)
97106
StylesMergingConfig(conflictingStyles = conflictingStyles.toTypedArray())
98107
}
99108
H3 -> {
100-
val conflictingStyles = mutableListOf(H1, H2, ORDERED_LIST, UNORDERED_LIST, BLOCK_QUOTE, CODE_BLOCK)
109+
val conflictingStyles = mutableListOf(H1, H2, H4, H5, H6, ORDERED_LIST, UNORDERED_LIST, BLOCK_QUOTE, CODE_BLOCK)
101110
if (htmlStyle.h3Bold) conflictingStyles.add(BOLD)
102111
StylesMergingConfig(conflictingStyles = conflictingStyles.toTypedArray())
103112
}
113+
H4 -> {
114+
val conflictingStyles = mutableListOf(H1, H2, H3, H5, H6, ORDERED_LIST, UNORDERED_LIST, BLOCK_QUOTE, CODE_BLOCK)
115+
if (htmlStyle.h4Bold) conflictingStyles.add(BOLD)
116+
StylesMergingConfig(conflictingStyles = conflictingStyles.toTypedArray())
117+
}
118+
H5 -> {
119+
val conflictingStyles = mutableListOf(H1, H2, H3, H4, H6, ORDERED_LIST, UNORDERED_LIST, BLOCK_QUOTE, CODE_BLOCK)
120+
if (htmlStyle.h5Bold) conflictingStyles.add(BOLD)
121+
StylesMergingConfig(conflictingStyles = conflictingStyles.toTypedArray())
122+
}
123+
H6 -> {
124+
val conflictingStyles = mutableListOf(H1, H2, H3, H4, H5, ORDERED_LIST, UNORDERED_LIST, BLOCK_QUOTE, CODE_BLOCK)
125+
if (htmlStyle.h6Bold) conflictingStyles.add(BOLD)
126+
StylesMergingConfig(conflictingStyles = conflictingStyles.toTypedArray())
127+
}
104128
BLOCK_QUOTE -> StylesMergingConfig(
105-
conflictingStyles = arrayOf(H1, H2, H3, CODE_BLOCK, ORDERED_LIST, UNORDERED_LIST)
129+
conflictingStyles = arrayOf(H1, H2, H3, H4, H5, H6, CODE_BLOCK, ORDERED_LIST, UNORDERED_LIST)
106130
)
107131
CODE_BLOCK -> StylesMergingConfig(
108-
conflictingStyles = arrayOf(H1, H2, H3, BOLD, ITALIC, UNDERLINE, STRIKETHROUGH, UNORDERED_LIST, ORDERED_LIST, BLOCK_QUOTE, INLINE_CODE)
132+
conflictingStyles = arrayOf(H1, H2, H3, H4, H5, H6, BOLD, ITALIC, UNDERLINE, STRIKETHROUGH, UNORDERED_LIST, ORDERED_LIST, BLOCK_QUOTE, INLINE_CODE)
109133
)
110134
UNORDERED_LIST -> StylesMergingConfig(
111-
conflictingStyles = arrayOf(H1, H2, H3, ORDERED_LIST, CODE_BLOCK, BLOCK_QUOTE)
135+
conflictingStyles = arrayOf(H1, H2, H3, H4, H5, H6, ORDERED_LIST, CODE_BLOCK, BLOCK_QUOTE)
112136
)
113137
ORDERED_LIST -> StylesMergingConfig(
114-
conflictingStyles = arrayOf(H1, H2, H3, UNORDERED_LIST, CODE_BLOCK, BLOCK_QUOTE),
138+
conflictingStyles = arrayOf(H1, H2, H3, H4, H5, H6, UNORDERED_LIST, CODE_BLOCK, BLOCK_QUOTE),
115139
)
116140
LINK -> StylesMergingConfig(
117141
blockingStyles = arrayOf(INLINE_CODE, CODE_BLOCK, MENTION)

android/src/main/java/com/swmansion/enriched/styles/HtmlStyle.kt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,15 @@ class HtmlStyle {
2828
var h3FontSize: Int = 56
2929
var h3Bold: Boolean = false
3030

31+
var h4FontSize: Int = 48
32+
var h4Bold: Boolean = false
33+
34+
var h5FontSize: Int = 40
35+
var h5Bold: Boolean = false
36+
37+
var h6FontSize: Int = 32
38+
var h6Bold: Boolean = false
39+
3140
var blockquoteColor: Int? = null
3241
var blockquoteBorderColor: Int = Color.BLACK
3342
var blockquoteStripeWidth: Int = 2
@@ -80,6 +89,18 @@ class HtmlStyle {
8089
h3FontSize = parseFloat(h3Style, "fontSize").toInt()
8190
h3Bold = h3Style?.getBoolean("bold") == true
8291

92+
val h4Style = style.getMap("h4")
93+
h4FontSize = parseFloat(h4Style, "fontSize").toInt()
94+
h4Bold = h4Style?.getBoolean("bold") == true
95+
96+
val h5Style = style.getMap("h5")
97+
h5FontSize = parseFloat(h5Style, "fontSize").toInt()
98+
h5Bold = h5Style?.getBoolean("bold") == true
99+
100+
val h6Style = style.getMap("h6")
101+
h6FontSize = parseFloat(h6Style, "fontSize").toInt()
102+
h6Bold = h6Style?.getBoolean("bold") == true
103+
83104
val blockquoteStyle = style.getMap("blockquote")
84105
blockquoteColor = parseOptionalColor(blockquoteStyle, "color")
85106
blockquoteBorderColor = parseColor(blockquoteStyle, "borderColor")

android/src/main/java/com/swmansion/enriched/utils/EnrichedParser.java

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
import com.swmansion.enriched.spans.EnrichedH1Span;
1717
import com.swmansion.enriched.spans.EnrichedH2Span;
1818
import com.swmansion.enriched.spans.EnrichedH3Span;
19+
import com.swmansion.enriched.spans.EnrichedH4Span;
20+
import com.swmansion.enriched.spans.EnrichedH5Span;
21+
import com.swmansion.enriched.spans.EnrichedH6Span;
1922
import com.swmansion.enriched.spans.EnrichedImageSpan;
2023
import com.swmansion.enriched.spans.EnrichedInlineCodeSpan;
2124
import com.swmansion.enriched.spans.EnrichedItalicSpan;
@@ -153,6 +156,12 @@ private static String getBlockTag(EnrichedParagraphSpan[] spans) {
153156
return "h2";
154157
} else if (span instanceof EnrichedH3Span) {
155158
return "h3";
159+
} else if (span instanceof EnrichedH4Span) {
160+
return "h4";
161+
} else if (span instanceof EnrichedH5Span) {
162+
return "h5";
163+
} else if (span instanceof EnrichedH6Span) {
164+
return "h6";
156165
}
157166
}
158167

@@ -452,6 +461,12 @@ private void handleStartTag(String tag, Attributes attributes) {
452461
startHeading(mSpannableStringBuilder, 2);
453462
} else if (tag.equalsIgnoreCase("h3")) {
454463
startHeading(mSpannableStringBuilder, 3);
464+
} else if (tag.equalsIgnoreCase("h4")) {
465+
startHeading(mSpannableStringBuilder, 4);
466+
} else if (tag.equalsIgnoreCase("h5")) {
467+
startHeading(mSpannableStringBuilder, 5);
468+
} else if (tag.equalsIgnoreCase("h6")) {
469+
startHeading(mSpannableStringBuilder, 6);
455470
} else if (tag.equalsIgnoreCase("img")) {
456471
startImg(mSpannableStringBuilder, attributes, mImageGetter, mStyle);
457472
} else if (tag.equalsIgnoreCase("code")) {
@@ -490,6 +505,12 @@ private void handleEndTag(String tag) {
490505
endHeading(mSpannableStringBuilder, mStyle, 2);
491506
} else if (tag.equalsIgnoreCase("h3")) {
492507
endHeading(mSpannableStringBuilder, mStyle, 3);
508+
} else if (tag.equalsIgnoreCase("h4")) {
509+
endHeading(mSpannableStringBuilder, mStyle, 4);
510+
} else if (tag.equalsIgnoreCase("h5")) {
511+
endHeading(mSpannableStringBuilder, mStyle, 5);
512+
} else if (tag.equalsIgnoreCase("h6")) {
513+
endHeading(mSpannableStringBuilder, mStyle, 6);
493514
} else if (tag.equalsIgnoreCase("code")) {
494515
end(mSpannableStringBuilder, Code.class, new EnrichedInlineCodeSpan(mStyle));
495516
} else if (tag.equalsIgnoreCase("mention")) {
@@ -593,6 +614,15 @@ private void startHeading(Editable text, int level) {
593614
case 3:
594615
start(text, new H3());
595616
break;
617+
case 4:
618+
start(text, new H4());
619+
break;
620+
case 5:
621+
start(text, new H5());
622+
break;
623+
case 6:
624+
start(text, new H6());
625+
break;
596626
default:
597627
throw new IllegalArgumentException("Unsupported heading level: " + level);
598628
}
@@ -614,6 +644,18 @@ private static void endHeading(Editable text, HtmlStyle style, int level) {
614644
H3 lastH3 = getLast(text, H3.class);
615645
setParagraphSpanFromMark(text, lastH3, new EnrichedH3Span(style));
616646
break;
647+
case 4:
648+
H4 lastH4 = getLast(text, H4.class);
649+
setParagraphSpanFromMark(text, lastH4, new EnrichedH4Span(style));
650+
break;
651+
case 5:
652+
H5 lastH5 = getLast(text, H5.class);
653+
setParagraphSpanFromMark(text, lastH5, new EnrichedH5Span(style));
654+
break;
655+
case 6:
656+
H6 lastH6 = getLast(text, H6.class);
657+
setParagraphSpanFromMark(text, lastH6, new EnrichedH6Span(style));
658+
break;
617659
default:
618660
throw new IllegalArgumentException("Unsupported heading level: " + level);
619661
}
@@ -807,6 +849,15 @@ private static class H2 {
807849
private static class H3 {
808850
}
809851

852+
private static class H4 {
853+
}
854+
855+
private static class H5 {
856+
}
857+
858+
private static class H6 {
859+
}
860+
810861
private static class Bold {
811862
}
812863

android/src/main/java/com/swmansion/enriched/utils/EnrichedSpanState.kt

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ class EnrichedSpanState(private val view: EnrichedTextInputView) {
2727
private set
2828
var h3Start: Int? = null
2929
private set
30+
var h4Start: Int? = null
31+
private set
32+
var h5Start: Int? = null
33+
private set
34+
var h6Start: Int? = null
35+
private set
3036
var codeBlockStart: Int? = null
3137
private set
3238
var blockQuoteStart: Int? = null
@@ -82,6 +88,21 @@ class EnrichedSpanState(private val view: EnrichedTextInputView) {
8288
emitStateChangeEvent()
8389
}
8490

91+
fun setH4Start(start: Int?) {
92+
this.h4Start = start
93+
emitStateChangeEvent()
94+
}
95+
96+
fun setH5Start(start: Int?) {
97+
this.h5Start = start
98+
emitStateChangeEvent()
99+
}
100+
101+
fun setH6Start(start: Int?) {
102+
this.h6Start = start
103+
emitStateChangeEvent()
104+
}
105+
85106
fun setCodeBlockStart(start: Int?) {
86107
this.codeBlockStart = start
87108
emitStateChangeEvent()
@@ -127,6 +148,9 @@ class EnrichedSpanState(private val view: EnrichedTextInputView) {
127148
EnrichedSpans.H1 -> h1Start
128149
EnrichedSpans.H2 -> h2Start
129150
EnrichedSpans.H3 -> h3Start
151+
EnrichedSpans.H4 -> h4Start
152+
EnrichedSpans.H5 -> h5Start
153+
EnrichedSpans.H6 -> h6Start
130154
EnrichedSpans.CODE_BLOCK -> codeBlockStart
131155
EnrichedSpans.BLOCK_QUOTE -> blockQuoteStart
132156
EnrichedSpans.ORDERED_LIST -> orderedListStart
@@ -150,6 +174,9 @@ class EnrichedSpanState(private val view: EnrichedTextInputView) {
150174
EnrichedSpans.H1 -> setH1Start(start)
151175
EnrichedSpans.H2 -> setH2Start(start)
152176
EnrichedSpans.H3 -> setH3Start(start)
177+
EnrichedSpans.H4 -> setH4Start(start)
178+
EnrichedSpans.H5 -> setH5Start(start)
179+
EnrichedSpans.H6 -> setH6Start(start)
153180
EnrichedSpans.CODE_BLOCK -> setCodeBlockStart(start)
154181
EnrichedSpans.BLOCK_QUOTE -> setBlockQuoteStart(start)
155182
EnrichedSpans.ORDERED_LIST -> setOrderedListStart(start)
@@ -170,6 +197,9 @@ class EnrichedSpanState(private val view: EnrichedTextInputView) {
170197
payload.putBoolean("isH1", h1Start != null)
171198
payload.putBoolean("isH2", h2Start != null)
172199
payload.putBoolean("isH3", h3Start != null)
200+
payload.putBoolean("isH4", h4Start != null)
201+
payload.putBoolean("isH5", h5Start != null)
202+
payload.putBoolean("isH6", h6Start != null)
173203
payload.putBoolean("isCodeBlock", codeBlockStart != null)
174204
payload.putBoolean("isBlockQuote", blockQuoteStart != null)
175205
payload.putBoolean("isOrderedList", orderedListStart != null)

0 commit comments

Comments
 (0)