Skip to content

Commit 56a24c6

Browse files
IvanIhnatsiukexploIFszydlovsky
authored
feat: add H4, H5 & H6 styles (#236)
### Description: In this PR, I added the missing H4, H5, and H6 headers to the enriched library to match the HTML structure. ### How to test? 1. Run the project 2. Write any text and apply H4/H5/H6 styles ### Videos Android: https://github.com/user-attachments/assets/2b1e6f86-6c8c-43e3-9ab4-95a9062b981d ### iOS: https://github.com/user-attachments/assets/1403a3c3-ac18-434e-8626-4100386978ab --------- Co-authored-by: Igor Furgala <exploif@icloud.com> Co-authored-by: Mikołaj Szydłowski <9szydlowski9@gmail.com>
1 parent 21442fd commit 56a24c6

27 files changed

Lines changed: 704 additions & 63 deletions

README.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -144,9 +144,7 @@ Supported styles:
144144
- underline
145145
- strikethrough
146146
- inline code
147-
- H1 heading
148-
- H2 heading
149-
- H3 heading
147+
- H1, H2, H3, H4, H5 and H6 headings
150148
- codeblock
151149
- blockquote
152150
- ordered list

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,9 @@ class EnrichedTextInputView : AppCompatEditText {
475475
EnrichedSpans.H1 -> paragraphStyles?.toggleStyle(EnrichedSpans.H1)
476476
EnrichedSpans.H2 -> paragraphStyles?.toggleStyle(EnrichedSpans.H2)
477477
EnrichedSpans.H3 -> paragraphStyles?.toggleStyle(EnrichedSpans.H3)
478+
EnrichedSpans.H4 -> paragraphStyles?.toggleStyle(EnrichedSpans.H4)
479+
EnrichedSpans.H5 -> paragraphStyles?.toggleStyle(EnrichedSpans.H5)
480+
EnrichedSpans.H6 -> paragraphStyles?.toggleStyle(EnrichedSpans.H6)
478481
EnrichedSpans.CODE_BLOCK -> paragraphStyles?.toggleStyle(EnrichedSpans.CODE_BLOCK)
479482
EnrichedSpans.BLOCK_QUOTE -> paragraphStyles?.toggleStyle(EnrichedSpans.BLOCK_QUOTE)
480483
EnrichedSpans.ORDERED_LIST -> listStyles?.toggleStyle(EnrichedSpans.ORDERED_LIST)
@@ -500,6 +503,9 @@ class EnrichedTextInputView : AppCompatEditText {
500503
EnrichedSpans.H1 -> paragraphStyles?.removeStyle(EnrichedSpans.H1, start, end)
501504
EnrichedSpans.H2 -> paragraphStyles?.removeStyle(EnrichedSpans.H2, start, end)
502505
EnrichedSpans.H3 -> paragraphStyles?.removeStyle(EnrichedSpans.H3, start, end)
506+
EnrichedSpans.H4 -> paragraphStyles?.removeStyle(EnrichedSpans.H4, start, end)
507+
EnrichedSpans.H5 -> paragraphStyles?.removeStyle(EnrichedSpans.H5, start, end)
508+
EnrichedSpans.H6 -> paragraphStyles?.removeStyle(EnrichedSpans.H6, start, end)
503509
EnrichedSpans.CODE_BLOCK -> paragraphStyles?.removeStyle(EnrichedSpans.CODE_BLOCK, start, end)
504510
EnrichedSpans.BLOCK_QUOTE -> paragraphStyles?.removeStyle(EnrichedSpans.BLOCK_QUOTE, start, end)
505511
EnrichedSpans.ORDERED_LIST -> listStyles?.removeStyle(EnrichedSpans.ORDERED_LIST, start, end)
@@ -524,6 +530,9 @@ class EnrichedTextInputView : AppCompatEditText {
524530
EnrichedSpans.H1 -> paragraphStyles?.getStyleRange()
525531
EnrichedSpans.H2 -> paragraphStyles?.getStyleRange()
526532
EnrichedSpans.H3 -> paragraphStyles?.getStyleRange()
533+
EnrichedSpans.H4 -> paragraphStyles?.getStyleRange()
534+
EnrichedSpans.H5 -> paragraphStyles?.getStyleRange()
535+
EnrichedSpans.H6 -> paragraphStyles?.getStyleRange()
527536
EnrichedSpans.CODE_BLOCK -> paragraphStyles?.getStyleRange()
528537
EnrichedSpans.BLOCK_QUOTE -> paragraphStyles?.getStyleRange()
529538
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
@@ -294,6 +294,18 @@ class EnrichedTextInputViewManager :
294294
view?.verifyAndToggleStyle(EnrichedSpans.H3)
295295
}
296296

297+
override fun toggleH4(view: EnrichedTextInputView?) {
298+
view?.verifyAndToggleStyle(EnrichedSpans.H4)
299+
}
300+
301+
override fun toggleH5(view: EnrichedTextInputView?) {
302+
view?.verifyAndToggleStyle(EnrichedSpans.H5)
303+
}
304+
305+
override fun toggleH6(view: EnrichedTextInputView?) {
306+
view?.verifyAndToggleStyle(EnrichedSpans.H6)
307+
}
308+
297309
override fun toggleCodeBlock(view: EnrichedTextInputView?) {
298310
view?.verifyAndToggleStyle(EnrichedSpans.CODE_BLOCK)
299311
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
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(
10+
private val htmlStyle: HtmlStyle,
11+
) : AbsoluteSizeSpan(htmlStyle.h4FontSize),
12+
EnrichedHeadingSpan {
13+
override val dependsOnHtmlStyle: Boolean = true
14+
15+
override fun updateDrawState(tp: TextPaint) {
16+
super.updateDrawState(tp)
17+
val bold = htmlStyle.h4Bold
18+
if (bold) {
19+
tp.typeface = Typeface.create(tp.typeface, Typeface.BOLD)
20+
}
21+
}
22+
23+
override fun rebuildWithStyle(htmlStyle: HtmlStyle): EnrichedH4Span = EnrichedH4Span(htmlStyle)
24+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
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(
10+
private val htmlStyle: HtmlStyle,
11+
) : AbsoluteSizeSpan(htmlStyle.h5FontSize),
12+
EnrichedHeadingSpan {
13+
override val dependsOnHtmlStyle: Boolean = true
14+
15+
override fun updateDrawState(tp: TextPaint) {
16+
super.updateDrawState(tp)
17+
val bold = htmlStyle.h5Bold
18+
if (bold) {
19+
tp.typeface = Typeface.create(tp.typeface, Typeface.BOLD)
20+
}
21+
}
22+
23+
override fun rebuildWithStyle(htmlStyle: HtmlStyle): EnrichedH5Span = EnrichedH5Span(htmlStyle)
24+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
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(
10+
private val htmlStyle: HtmlStyle,
11+
) : AbsoluteSizeSpan(htmlStyle.h6FontSize),
12+
EnrichedHeadingSpan {
13+
override val dependsOnHtmlStyle: Boolean = true
14+
15+
override fun updateDrawState(tp: TextPaint) {
16+
super.updateDrawState(tp)
17+
val bold = htmlStyle.h6Bold
18+
if (bold) {
19+
tp.typeface = Typeface.create(tp.typeface, Typeface.BOLD)
20+
}
21+
}
22+
23+
override fun rebuildWithStyle(htmlStyle: HtmlStyle): EnrichedH6Span = EnrichedH6Span(htmlStyle)
24+
}

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

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ object EnrichedSpans {
3939
const val H1 = "h1"
4040
const val H2 = "h2"
4141
const val H3 = "h3"
42+
const val H4 = "h4"
43+
const val H5 = "h5"
44+
const val H6 = "h6"
4245
const val BLOCK_QUOTE = "block_quote"
4346
const val CODE_BLOCK = "code_block"
4447

@@ -65,6 +68,9 @@ object EnrichedSpans {
6568
H1 to ParagraphSpanConfig(EnrichedH1Span::class.java, false),
6669
H2 to ParagraphSpanConfig(EnrichedH2Span::class.java, false),
6770
H3 to ParagraphSpanConfig(EnrichedH3Span::class.java, false),
71+
H4 to ParagraphSpanConfig(EnrichedH4Span::class.java, false),
72+
H5 to ParagraphSpanConfig(EnrichedH5Span::class.java, false),
73+
H6 to ParagraphSpanConfig(EnrichedH6Span::class.java, false),
6874
BLOCK_QUOTE to ParagraphSpanConfig(EnrichedBlockQuoteSpan::class.java, true),
6975
CODE_BLOCK to ParagraphSpanConfig(EnrichedCodeBlockSpan::class.java, true),
7076
)
@@ -94,6 +100,9 @@ object EnrichedSpans {
94100
if (htmlStyle.h1Bold) blockingStyles.add(H1)
95101
if (htmlStyle.h2Bold) blockingStyles.add(H2)
96102
if (htmlStyle.h3Bold) blockingStyles.add(H3)
103+
if (htmlStyle.h4Bold) blockingStyles.add(H4)
104+
if (htmlStyle.h5Bold) blockingStyles.add(H5)
105+
if (htmlStyle.h6Bold) blockingStyles.add(H6)
97106
StylesMergingConfig(blockingStyles = blockingStyles.toTypedArray())
98107
}
99108

@@ -123,26 +132,44 @@ object EnrichedSpans {
123132
}
124133

125134
H1 -> {
126-
val conflictingStyles = mutableListOf(H2, H3, ORDERED_LIST, UNORDERED_LIST, BLOCK_QUOTE, CODE_BLOCK)
135+
val conflictingStyles = mutableListOf(H2, H3, H4, H5, H6, ORDERED_LIST, UNORDERED_LIST, BLOCK_QUOTE, CODE_BLOCK)
127136
if (htmlStyle.h1Bold) conflictingStyles.add(BOLD)
128137
StylesMergingConfig(conflictingStyles = conflictingStyles.toTypedArray())
129138
}
130139

131140
H2 -> {
132-
val conflictingStyles = mutableListOf(H1, H3, ORDERED_LIST, UNORDERED_LIST, BLOCK_QUOTE, CODE_BLOCK)
141+
val conflictingStyles = mutableListOf(H1, H3, H4, H5, H6, ORDERED_LIST, UNORDERED_LIST, BLOCK_QUOTE, CODE_BLOCK)
133142
if (htmlStyle.h2Bold) conflictingStyles.add(BOLD)
134143
StylesMergingConfig(conflictingStyles = conflictingStyles.toTypedArray())
135144
}
136145

137146
H3 -> {
138-
val conflictingStyles = mutableListOf(H1, H2, ORDERED_LIST, UNORDERED_LIST, BLOCK_QUOTE, CODE_BLOCK)
147+
val conflictingStyles = mutableListOf(H1, H2, H4, H5, H6, ORDERED_LIST, UNORDERED_LIST, BLOCK_QUOTE, CODE_BLOCK)
139148
if (htmlStyle.h3Bold) conflictingStyles.add(BOLD)
140149
StylesMergingConfig(conflictingStyles = conflictingStyles.toTypedArray())
141150
}
142151

152+
H4 -> {
153+
val conflictingStyles = mutableListOf(H1, H2, H3, H5, H6, ORDERED_LIST, UNORDERED_LIST, BLOCK_QUOTE, CODE_BLOCK)
154+
if (htmlStyle.h4Bold) conflictingStyles.add(BOLD)
155+
StylesMergingConfig(conflictingStyles = conflictingStyles.toTypedArray())
156+
}
157+
158+
H5 -> {
159+
val conflictingStyles = mutableListOf(H1, H2, H3, H4, H6, ORDERED_LIST, UNORDERED_LIST, BLOCK_QUOTE, CODE_BLOCK)
160+
if (htmlStyle.h5Bold) conflictingStyles.add(BOLD)
161+
StylesMergingConfig(conflictingStyles = conflictingStyles.toTypedArray())
162+
}
163+
164+
H6 -> {
165+
val conflictingStyles = mutableListOf(H1, H2, H3, H4, H5, ORDERED_LIST, UNORDERED_LIST, BLOCK_QUOTE, CODE_BLOCK)
166+
if (htmlStyle.h6Bold) conflictingStyles.add(BOLD)
167+
StylesMergingConfig(conflictingStyles = conflictingStyles.toTypedArray())
168+
}
169+
143170
BLOCK_QUOTE -> {
144171
StylesMergingConfig(
145-
conflictingStyles = arrayOf(H1, H2, H3, CODE_BLOCK, ORDERED_LIST, UNORDERED_LIST),
172+
conflictingStyles = arrayOf(H1, H2, H3, H4, H5, H6, CODE_BLOCK, ORDERED_LIST, UNORDERED_LIST),
146173
)
147174
}
148175

@@ -153,6 +180,9 @@ object EnrichedSpans {
153180
H1,
154181
H2,
155182
H3,
183+
H4,
184+
H5,
185+
H6,
156186
BOLD,
157187
ITALIC,
158188
UNDERLINE,
@@ -167,13 +197,13 @@ object EnrichedSpans {
167197

168198
UNORDERED_LIST -> {
169199
StylesMergingConfig(
170-
conflictingStyles = arrayOf(H1, H2, H3, ORDERED_LIST, CODE_BLOCK, BLOCK_QUOTE),
200+
conflictingStyles = arrayOf(H1, H2, H3, H4, H5, H6, ORDERED_LIST, CODE_BLOCK, BLOCK_QUOTE),
171201
)
172202
}
173203

174204
ORDERED_LIST -> {
175205
StylesMergingConfig(
176-
conflictingStyles = arrayOf(H1, H2, H3, UNORDERED_LIST, CODE_BLOCK, BLOCK_QUOTE),
206+
conflictingStyles = arrayOf(H1, H2, H3, H4, H5, H6, UNORDERED_LIST, CODE_BLOCK, BLOCK_QUOTE),
177207
)
178208
}
179209

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

Lines changed: 33 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
@@ -77,6 +86,18 @@ class HtmlStyle {
7786
h3FontSize = parseFloat(h3Style, "fontSize").toInt()
7887
h3Bold = h3Style?.getBoolean("bold") == true
7988

89+
val h4Style = style.getMap("h4")
90+
h4FontSize = parseFloat(h4Style, "fontSize").toInt()
91+
h4Bold = h4Style?.getBoolean("bold") == true
92+
93+
val h5Style = style.getMap("h5")
94+
h5FontSize = parseFloat(h5Style, "fontSize").toInt()
95+
h5Bold = h5Style?.getBoolean("bold") == true
96+
97+
val h6Style = style.getMap("h6")
98+
h6FontSize = parseFloat(h6Style, "fontSize").toInt()
99+
h6Bold = h6Style?.getBoolean("bold") == true
100+
80101
val blockquoteStyle = style.getMap("blockquote")
81102
blockquoteColor = parseOptionalColor(blockquoteStyle, "color")
82103
blockquoteBorderColor = parseColor(blockquoteStyle, "borderColor")
@@ -247,6 +268,12 @@ class HtmlStyle {
247268
h2Bold == other.h2Bold &&
248269
h3FontSize == other.h3FontSize &&
249270
h3Bold == other.h3Bold &&
271+
h4FontSize == other.h4FontSize &&
272+
h4Bold == other.h4Bold &&
273+
h5FontSize == other.h5FontSize &&
274+
h5Bold == other.h5Bold &&
275+
h6FontSize == other.h6FontSize &&
276+
h6Bold == other.h6Bold &&
250277

251278
blockquoteColor == other.blockquoteColor &&
252279
blockquoteBorderColor == other.blockquoteBorderColor &&
@@ -283,6 +310,12 @@ class HtmlStyle {
283310
result = 31 * result + h2Bold.hashCode()
284311
result = 31 * result + h3FontSize.hashCode()
285312
result = 31 * result + h3Bold.hashCode()
313+
result = 31 * result + h4FontSize.hashCode()
314+
result = 31 * result + h4Bold.hashCode()
315+
result = 31 * result + h5FontSize.hashCode()
316+
result = 31 * result + h5Bold.hashCode()
317+
result = 31 * result + h6FontSize.hashCode()
318+
result = 31 * result + h6Bold.hashCode()
286319

287320
result = 31 * result + (blockquoteColor ?: 0)
288321
result = 31 * result + blockquoteBorderColor.hashCode()

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

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
import com.swmansion.enriched.spans.EnrichedH1Span;
1616
import com.swmansion.enriched.spans.EnrichedH2Span;
1717
import com.swmansion.enriched.spans.EnrichedH3Span;
18+
import com.swmansion.enriched.spans.EnrichedH4Span;
19+
import com.swmansion.enriched.spans.EnrichedH5Span;
20+
import com.swmansion.enriched.spans.EnrichedH6Span;
1821
import com.swmansion.enriched.spans.EnrichedImageSpan;
1922
import com.swmansion.enriched.spans.EnrichedInlineCodeSpan;
2023
import com.swmansion.enriched.spans.EnrichedItalicSpan;
@@ -159,6 +162,12 @@ private static String getBlockTag(EnrichedParagraphSpan[] spans) {
159162
return "h2";
160163
} else if (span instanceof EnrichedH3Span) {
161164
return "h3";
165+
} else if (span instanceof EnrichedH4Span) {
166+
return "h4";
167+
} else if (span instanceof EnrichedH5Span) {
168+
return "h5";
169+
} else if (span instanceof EnrichedH6Span) {
170+
return "h6";
162171
}
163172
}
164173

@@ -477,6 +486,12 @@ private void handleStartTag(String tag, Attributes attributes) {
477486
startHeading(mSpannableStringBuilder, 2);
478487
} else if (tag.equalsIgnoreCase("h3")) {
479488
startHeading(mSpannableStringBuilder, 3);
489+
} else if (tag.equalsIgnoreCase("h4")) {
490+
startHeading(mSpannableStringBuilder, 4);
491+
} else if (tag.equalsIgnoreCase("h5")) {
492+
startHeading(mSpannableStringBuilder, 5);
493+
} else if (tag.equalsIgnoreCase("h6")) {
494+
startHeading(mSpannableStringBuilder, 6);
480495
} else if (tag.equalsIgnoreCase("img")) {
481496
startImg(mSpannableStringBuilder, attributes, mImageGetter);
482497
} else if (tag.equalsIgnoreCase("code")) {
@@ -515,6 +530,12 @@ private void handleEndTag(String tag) {
515530
endHeading(mSpannableStringBuilder, mStyle, 2);
516531
} else if (tag.equalsIgnoreCase("h3")) {
517532
endHeading(mSpannableStringBuilder, mStyle, 3);
533+
} else if (tag.equalsIgnoreCase("h4")) {
534+
endHeading(mSpannableStringBuilder, mStyle, 4);
535+
} else if (tag.equalsIgnoreCase("h5")) {
536+
endHeading(mSpannableStringBuilder, mStyle, 5);
537+
} else if (tag.equalsIgnoreCase("h6")) {
538+
endHeading(mSpannableStringBuilder, mStyle, 6);
518539
} else if (tag.equalsIgnoreCase("code")) {
519540
end(mSpannableStringBuilder, Code.class, new EnrichedInlineCodeSpan(mStyle));
520541
} else if (tag.equalsIgnoreCase("mention")) {
@@ -618,6 +639,15 @@ private void startHeading(Editable text, int level) {
618639
case 3:
619640
start(text, new H3());
620641
break;
642+
case 4:
643+
start(text, new H4());
644+
break;
645+
case 5:
646+
start(text, new H5());
647+
break;
648+
case 6:
649+
start(text, new H6());
650+
break;
621651
default:
622652
throw new IllegalArgumentException("Unsupported heading level: " + level);
623653
}
@@ -639,6 +669,18 @@ private static void endHeading(Editable text, HtmlStyle style, int level) {
639669
H3 lastH3 = getLast(text, H3.class);
640670
setParagraphSpanFromMark(text, lastH3, new EnrichedH3Span(style));
641671
break;
672+
case 4:
673+
H4 lastH4 = getLast(text, H4.class);
674+
setParagraphSpanFromMark(text, lastH4, new EnrichedH4Span(style));
675+
break;
676+
case 5:
677+
H5 lastH5 = getLast(text, H5.class);
678+
setParagraphSpanFromMark(text, lastH5, new EnrichedH5Span(style));
679+
break;
680+
case 6:
681+
H6 lastH6 = getLast(text, H6.class);
682+
setParagraphSpanFromMark(text, lastH6, new EnrichedH6Span(style));
683+
break;
642684
default:
643685
throw new IllegalArgumentException("Unsupported heading level: " + level);
644686
}
@@ -819,6 +861,12 @@ private static class H2 {}
819861

820862
private static class H3 {}
821863

864+
private static class H4 {}
865+
866+
private static class H5 {}
867+
868+
private static class H6 {}
869+
822870
private static class Bold {}
823871

824872
private static class Italic {}

0 commit comments

Comments
 (0)