From 11f072edc280b7af8363bf3aaf7a06942a834716 Mon Sep 17 00:00:00 2001 From: Daniel Leal Date: Wed, 13 Dec 2017 15:34:31 +0100 Subject: [PATCH] [Header formatting][New]: Implement more complete configuration for header section (H1, H2, ..., H6) In original version, we could only configure the line break color and thickness, for H1 and H2. With this new implementation, we can set individual settings for all headers, with the options being: - Text color - Text font - Text size --- .../ru/noties/markwon/spans/HeadingSpan.java | 4 +- .../noties/markwon/spans/SpannableTheme.java | 102 +++++++++++------- .../markwon/spans/heading/HeadingConfig.kt | 83 ++++++++++++++ .../markwon/spans/heading/HeadingType.java | 27 +++++ 4 files changed, 179 insertions(+), 37 deletions(-) create mode 100644 library/src/main/java/ru/noties/markwon/spans/heading/HeadingConfig.kt create mode 100644 library/src/main/java/ru/noties/markwon/spans/heading/HeadingType.java diff --git a/library/src/main/java/ru/noties/markwon/spans/HeadingSpan.java b/library/src/main/java/ru/noties/markwon/spans/HeadingSpan.java index 6293d554..365fa7cb 100644 --- a/library/src/main/java/ru/noties/markwon/spans/HeadingSpan.java +++ b/library/src/main/java/ru/noties/markwon/spans/HeadingSpan.java @@ -10,12 +10,14 @@ import android.text.TextPaint; import android.text.style.LeadingMarginSpan; import android.text.style.MetricAffectingSpan; +import ru.noties.markwon.spans.heading.HeadingType; + public class HeadingSpan extends MetricAffectingSpan implements LeadingMarginSpan { private final SpannableTheme theme; private final Rect rect = ObjectsPool.rect(); private final Paint paint = ObjectsPool.paint(); - private final int level; + @HeadingType private final int level; public HeadingSpan(@NonNull SpannableTheme theme, @IntRange(from = 1, to = 6) int level) { this.theme = theme; diff --git a/library/src/main/java/ru/noties/markwon/spans/SpannableTheme.java b/library/src/main/java/ru/noties/markwon/spans/SpannableTheme.java index c402e24f..df6ab515 100644 --- a/library/src/main/java/ru/noties/markwon/spans/SpannableTheme.java +++ b/library/src/main/java/ru/noties/markwon/spans/SpannableTheme.java @@ -9,12 +9,15 @@ import android.support.annotation.AttrRes; import android.support.annotation.ColorInt; import android.support.annotation.Dimension; import android.support.annotation.FloatRange; -import android.support.annotation.IntRange; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.text.TextPaint; import android.util.TypedValue; +import ru.noties.markwon.spans.heading.HeadingConfig; +import ru.noties.markwon.spans.heading.HeadingType; +import ru.noties.markwon.spans.heading.HeadingTypeConfig; + @SuppressWarnings("WeakerAccess") public class SpannableTheme { @@ -78,16 +81,16 @@ public class SpannableTheme { final Dip dip = new Dip(context); return new Builder() - .linkColor(linkColor) - .codeMultilineMargin(dip.toPx(8)) - .blockMargin(dip.toPx(24)) - .blockQuoteWidth(dip.toPx(4)) - .bulletListItemStrokeWidth(dip.toPx(1)) - .headingBreakHeight(dip.toPx(1)) - .thematicBreakHeight(dip.toPx(4)) - .tableCellPadding(dip.toPx(4)) - .tableBorderWidth(dip.toPx(1)) - .taskListDrawable(new TaskListDrawable(linkColor, linkColor, backgroundColor)); + .linkColor(linkColor) + .codeMultilineMargin(dip.toPx(8)) + .blockMargin(dip.toPx(24)) + .blockQuoteWidth(dip.toPx(4)) + .bulletListItemStrokeWidth(dip.toPx(1)) + .headingConfig(new HeadingConfig(), dip.density) + .thematicBreakHeight(dip.toPx(4)) + .tableCellPadding(dip.toPx(4)) + .tableBorderWidth(dip.toPx(1)) + .taskListDrawable(new TaskListDrawable(linkColor, linkColor, backgroundColor)); } private static int resolve(Context context, @AttrRes int attr) { @@ -104,6 +107,7 @@ public class SpannableTheme { protected static final int BLOCK_QUOTE_DEF_COLOR_ALPHA = 25; protected static final int CODE_DEF_BACKGROUND_COLOR_ALPHA = 25; + protected static final float CODE_DEF_TEXT_SIZE_RATIO = .87F; protected static final int HEADING_DEF_BREAK_COLOR_ALPHA = 75; @@ -111,7 +115,7 @@ public class SpannableTheme { // taken from html spec (most browsers render headings like that) // is not exposed via protected modifier in order to disallow modification private static final float[] HEADING_SIZES = { - 2.F, 1.5F, 1.17F, 1.F, .83F, .67F, + 2.F, 1.5F, 1.17F, 1.F, .83F, .67F, }; protected static final float SCRIPT_DEF_TEXT_SIZE_RATIO = .75F; @@ -159,11 +163,7 @@ public class SpannableTheme { // applied ONLY if default typeface was used, otherwise, not applied protected final int codeTextSize; - // by default paint.getStrokeWidth - protected final int headingBreakHeight; - - // by default, text color with `HEADING_DEF_BREAK_COLOR_ALPHA` applied alpha - protected final int headingBreakColor; + protected final HeadingConfig headingConfig; // by default `SCRIPT_DEF_TEXT_SIZE_RATIO` protected final float scriptTextSizeRatio; @@ -202,8 +202,7 @@ public class SpannableTheme { this.codeMultilineMargin = builder.codeMultilineMargin; this.codeTypeface = builder.codeTypeface; this.codeTextSize = builder.codeTextSize; - this.headingBreakHeight = builder.headingBreakHeight; - this.headingBreakColor = builder.headingBreakColor; + this.headingConfig = builder.headingConfig; this.scriptTextSizeRatio = builder.scriptTextSizeRatio; this.thematicBreakColor = builder.thematicBreakColor; this.thematicBreakHeight = builder.thematicBreakHeight; @@ -214,7 +213,6 @@ public class SpannableTheme { this.taskListDrawable = builder.taskListDrawable; } - public void applyLinkStyle(@NonNull Paint paint) { paint.setUnderlineText(true); if (linkColor != 0) { @@ -269,7 +267,7 @@ public class SpannableTheme { final int width; if (bulletWidth == 0 - || bulletWidth > min) { + || bulletWidth > min) { width = min; } else { width = bulletWidth; @@ -322,13 +320,51 @@ public class SpannableTheme { return color; } - public void applyHeadingTextStyle(@NonNull Paint paint, @IntRange(from = 1, to = 6) int level) { + public void applyHeadingTextStyle(@NonNull Paint paint, @HeadingType int level) { + HeadingTypeConfig headingTypeConfig; + switch (level) { + case HeadingType.H1: + headingTypeConfig = headingConfig.getH1Config(); + break; + case HeadingType.H2: + headingTypeConfig = headingConfig.getH2Config(); + break; + case HeadingType.H3: + headingTypeConfig = headingConfig.getH3Config(); + break; + case HeadingType.H4: + headingTypeConfig = headingConfig.getH4Config(); + break; + case HeadingType.H5: + headingTypeConfig = headingConfig.getH5Config(); + break; + case HeadingType.H6: + headingTypeConfig = headingConfig.getH6Config(); + break; + default: + headingTypeConfig = new HeadingTypeConfig(); + } + paint.setFakeBoldText(true); - paint.setTextSize(paint.getTextSize() * HEADING_SIZES[level - 1]); + + final float textSize = headingTypeConfig.getTextSize() > 0 ? + headingTypeConfig.getTextSize() : HEADING_SIZES[level - 1]; + paint.setTextSize(paint.getTextSize() * textSize); + + final int textColor = headingTypeConfig.getTextColor(); + if (textColor != -1) { + paint.setColor(textColor); + } + + final Typeface typeface = headingTypeConfig.getTypeface(); + if(typeface != null){ + paint.setTypeface(typeface); + } } public void applyHeadingBreakStyle(@NonNull Paint paint) { final int color; + final int headingBreakColor = headingConfig.getHeadingBreakConfig().getHeadingBreakColor(); if (headingBreakColor != 0) { color = headingBreakColor; } else { @@ -336,6 +372,7 @@ public class SpannableTheme { } paint.setColor(color); paint.setStyle(Paint.Style.FILL); + final float headingBreakHeight = headingConfig.getHeadingBreakConfig().getHeadingBreakStrokeWidth(); if (headingBreakHeight >= 0) { //noinspection SuspiciousNameCombination paint.setStrokeWidth(headingBreakHeight); @@ -441,8 +478,7 @@ public class SpannableTheme { private int codeMultilineMargin; private Typeface codeTypeface; private int codeTextSize; - private int headingBreakHeight = -1; - private int headingBreakColor; + private HeadingConfig headingConfig; private float scriptTextSizeRatio; private int thematicBreakColor; private int thematicBreakHeight = -1; @@ -468,8 +504,7 @@ public class SpannableTheme { this.codeMultilineMargin = theme.codeMultilineMargin; this.codeTypeface = theme.codeTypeface; this.codeTextSize = theme.codeTextSize; - this.headingBreakHeight = theme.headingBreakHeight; - this.headingBreakColor = theme.headingBreakColor; + this.headingConfig = theme.headingConfig; this.scriptTextSizeRatio = theme.scriptTextSizeRatio; this.thematicBreakColor = theme.thematicBreakColor; this.thematicBreakHeight = theme.thematicBreakHeight; @@ -553,14 +588,9 @@ public class SpannableTheme { } @NonNull - public Builder headingBreakHeight(@Dimension int headingBreakHeight) { - this.headingBreakHeight = headingBreakHeight; - return this; - } - - @NonNull - public Builder headingBreakColor(@ColorInt int headingBreakColor) { - this.headingBreakColor = headingBreakColor; + public Builder headingConfig(HeadingConfig headingConfig, float density) { + headingConfig.setDensityFactor(density); + this.headingConfig = headingConfig; return this; } @@ -630,7 +660,7 @@ public class SpannableTheme { private static class Dip { - private final float density; + protected final float density; Dip(@NonNull Context context) { this.density = context.getResources().getDisplayMetrics().density; diff --git a/library/src/main/java/ru/noties/markwon/spans/heading/HeadingConfig.kt b/library/src/main/java/ru/noties/markwon/spans/heading/HeadingConfig.kt new file mode 100644 index 00000000..6676dc20 --- /dev/null +++ b/library/src/main/java/ru/noties/markwon/spans/heading/HeadingConfig.kt @@ -0,0 +1,83 @@ +package ru.noties.markwon.spans.heading + +import android.graphics.Typeface +import android.support.annotation.ColorInt +import android.support.annotation.Dimension + +/** + * Configuration for heading type (H1, H2, ..., H6) + * + * Can define different configurations for all six types of headings, + * plus an extra configuration for the line breaks (only applies to H1 and H2) + * + * @property h1Config Config for H1 heading + * @property h2Config Config for H2 heading + * @property h3Config Config for H3 heading + * @property h4Config Config for H4 heading + * @property h5Config Config for H5 heading + * @property h6Config Config for H6 heading + * @property headingBreakConfig Config for line breaks (for H1 and H2) + */ +data class HeadingConfig @JvmOverloads constructor( + val h1Config: HeadingTypeConfig = HeadingTypeConfig(), + val h2Config: HeadingTypeConfig = HeadingTypeConfig(), + val h3Config: HeadingTypeConfig = HeadingTypeConfig(), + val h4Config: HeadingTypeConfig = HeadingTypeConfig(), + val h5Config: HeadingTypeConfig = HeadingTypeConfig(), + val h6Config: HeadingTypeConfig = HeadingTypeConfig(), + val headingBreakConfig: HeadingBreakConfig = HeadingBreakConfig() +) { + fun setDensityFactor(factor: Float) { + h1Config.densityFactor = factor + h2Config.densityFactor = factor + h3Config.densityFactor = factor + h4Config.densityFactor = factor + h5Config.densityFactor = factor + h6Config.densityFactor = factor + headingBreakConfig.densityFactor = factor + } +} + +/** + * Configuration for given heading type (H1, H2, ..., H6) + * + * Can set text size, text color and font (typeface) + * + * @property textSize Text size for heading + * @property textColor Text color for heading + * @property typeface Text size for heading + */ +class HeadingTypeConfig @JvmOverloads constructor( + //Standard sizes available at #SpannableTheme.java:HEADING_SIZES + textSize: Float = -1F, + + @ColorInt val textColor: Int = -1, + + val typeface: Typeface? = null +) { + internal var densityFactor: Float = -1F + + val textSize: Float = textSize + get() = field * densityFactor +} + +/** + * Configuration for given heading type (H1, H2, ..., H6) + * + * Can set text size, text color and font (typeface) + * + * @property headingBreakStrokeWidth Stroke width for heading's line break + * @property headingBreakColor Color for heading's line break + */ +class HeadingBreakConfig @JvmOverloads constructor( + // by default paint.getStrokeWidth + @Dimension headingBreakStrokeWidth: Int = -1, + + // by default, text color with `HEADING_DEF_BREAK_COLOR_ALPHA` applied alpha + @ColorInt val headingBreakColor: Int = 0 +) { + internal var densityFactor: Float = -1F + + val headingBreakStrokeWidth: Float = headingBreakStrokeWidth.toFloat() + get() = field * densityFactor +} \ No newline at end of file diff --git a/library/src/main/java/ru/noties/markwon/spans/heading/HeadingType.java b/library/src/main/java/ru/noties/markwon/spans/heading/HeadingType.java new file mode 100644 index 00000000..38a2f460 --- /dev/null +++ b/library/src/main/java/ru/noties/markwon/spans/heading/HeadingType.java @@ -0,0 +1,27 @@ +package ru.noties.markwon.spans.heading; + +import android.support.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import static ru.noties.markwon.spans.heading.HeadingType.H1; +import static ru.noties.markwon.spans.heading.HeadingType.H2; +import static ru.noties.markwon.spans.heading.HeadingType.H3; +import static ru.noties.markwon.spans.heading.HeadingType.H4; +import static ru.noties.markwon.spans.heading.HeadingType.H5; +import static ru.noties.markwon.spans.heading.HeadingType.H6; + +/** + * Created by daniel.leal on 13.12.17. + */ +@Retention(RetentionPolicy.SOURCE) +@IntDef({H1, H2, H3, H4, H5, H6}) +public @interface HeadingType { + int H1 = 1; + int H2 = 2; + int H3 = 3; + int H4 = 4; + int H5 = 5; + int H6 = 6; +}