[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
This commit is contained in:
Daniel Leal 2017-12-13 15:34:31 +01:00
parent 4d0336c13b
commit 11f072edc2
4 changed files with 179 additions and 37 deletions

View File

@ -10,12 +10,14 @@ import android.text.TextPaint;
import android.text.style.LeadingMarginSpan; import android.text.style.LeadingMarginSpan;
import android.text.style.MetricAffectingSpan; import android.text.style.MetricAffectingSpan;
import ru.noties.markwon.spans.heading.HeadingType;
public class HeadingSpan extends MetricAffectingSpan implements LeadingMarginSpan { public class HeadingSpan extends MetricAffectingSpan implements LeadingMarginSpan {
private final SpannableTheme theme; private final SpannableTheme theme;
private final Rect rect = ObjectsPool.rect(); private final Rect rect = ObjectsPool.rect();
private final Paint paint = ObjectsPool.paint(); 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) { public HeadingSpan(@NonNull SpannableTheme theme, @IntRange(from = 1, to = 6) int level) {
this.theme = theme; this.theme = theme;

View File

@ -9,12 +9,15 @@ import android.support.annotation.AttrRes;
import android.support.annotation.ColorInt; import android.support.annotation.ColorInt;
import android.support.annotation.Dimension; import android.support.annotation.Dimension;
import android.support.annotation.FloatRange; import android.support.annotation.FloatRange;
import android.support.annotation.IntRange;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.text.TextPaint; import android.text.TextPaint;
import android.util.TypedValue; 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") @SuppressWarnings("WeakerAccess")
public class SpannableTheme { public class SpannableTheme {
@ -78,16 +81,16 @@ public class SpannableTheme {
final Dip dip = new Dip(context); final Dip dip = new Dip(context);
return new Builder() return new Builder()
.linkColor(linkColor) .linkColor(linkColor)
.codeMultilineMargin(dip.toPx(8)) .codeMultilineMargin(dip.toPx(8))
.blockMargin(dip.toPx(24)) .blockMargin(dip.toPx(24))
.blockQuoteWidth(dip.toPx(4)) .blockQuoteWidth(dip.toPx(4))
.bulletListItemStrokeWidth(dip.toPx(1)) .bulletListItemStrokeWidth(dip.toPx(1))
.headingBreakHeight(dip.toPx(1)) .headingConfig(new HeadingConfig(), dip.density)
.thematicBreakHeight(dip.toPx(4)) .thematicBreakHeight(dip.toPx(4))
.tableCellPadding(dip.toPx(4)) .tableCellPadding(dip.toPx(4))
.tableBorderWidth(dip.toPx(1)) .tableBorderWidth(dip.toPx(1))
.taskListDrawable(new TaskListDrawable(linkColor, linkColor, backgroundColor)); .taskListDrawable(new TaskListDrawable(linkColor, linkColor, backgroundColor));
} }
private static int resolve(Context context, @AttrRes int attr) { 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 BLOCK_QUOTE_DEF_COLOR_ALPHA = 25;
protected static final int CODE_DEF_BACKGROUND_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 float CODE_DEF_TEXT_SIZE_RATIO = .87F;
protected static final int HEADING_DEF_BREAK_COLOR_ALPHA = 75; 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) // taken from html spec (most browsers render headings like that)
// is not exposed via protected modifier in order to disallow modification // is not exposed via protected modifier in order to disallow modification
private static final float[] HEADING_SIZES = { 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; 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 // applied ONLY if default typeface was used, otherwise, not applied
protected final int codeTextSize; protected final int codeTextSize;
// by default paint.getStrokeWidth protected final HeadingConfig headingConfig;
protected final int headingBreakHeight;
// by default, text color with `HEADING_DEF_BREAK_COLOR_ALPHA` applied alpha
protected final int headingBreakColor;
// by default `SCRIPT_DEF_TEXT_SIZE_RATIO` // by default `SCRIPT_DEF_TEXT_SIZE_RATIO`
protected final float scriptTextSizeRatio; protected final float scriptTextSizeRatio;
@ -202,8 +202,7 @@ public class SpannableTheme {
this.codeMultilineMargin = builder.codeMultilineMargin; this.codeMultilineMargin = builder.codeMultilineMargin;
this.codeTypeface = builder.codeTypeface; this.codeTypeface = builder.codeTypeface;
this.codeTextSize = builder.codeTextSize; this.codeTextSize = builder.codeTextSize;
this.headingBreakHeight = builder.headingBreakHeight; this.headingConfig = builder.headingConfig;
this.headingBreakColor = builder.headingBreakColor;
this.scriptTextSizeRatio = builder.scriptTextSizeRatio; this.scriptTextSizeRatio = builder.scriptTextSizeRatio;
this.thematicBreakColor = builder.thematicBreakColor; this.thematicBreakColor = builder.thematicBreakColor;
this.thematicBreakHeight = builder.thematicBreakHeight; this.thematicBreakHeight = builder.thematicBreakHeight;
@ -214,7 +213,6 @@ public class SpannableTheme {
this.taskListDrawable = builder.taskListDrawable; this.taskListDrawable = builder.taskListDrawable;
} }
public void applyLinkStyle(@NonNull Paint paint) { public void applyLinkStyle(@NonNull Paint paint) {
paint.setUnderlineText(true); paint.setUnderlineText(true);
if (linkColor != 0) { if (linkColor != 0) {
@ -269,7 +267,7 @@ public class SpannableTheme {
final int width; final int width;
if (bulletWidth == 0 if (bulletWidth == 0
|| bulletWidth > min) { || bulletWidth > min) {
width = min; width = min;
} else { } else {
width = bulletWidth; width = bulletWidth;
@ -322,13 +320,51 @@ public class SpannableTheme {
return color; 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.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) { public void applyHeadingBreakStyle(@NonNull Paint paint) {
final int color; final int color;
final int headingBreakColor = headingConfig.getHeadingBreakConfig().getHeadingBreakColor();
if (headingBreakColor != 0) { if (headingBreakColor != 0) {
color = headingBreakColor; color = headingBreakColor;
} else { } else {
@ -336,6 +372,7 @@ public class SpannableTheme {
} }
paint.setColor(color); paint.setColor(color);
paint.setStyle(Paint.Style.FILL); paint.setStyle(Paint.Style.FILL);
final float headingBreakHeight = headingConfig.getHeadingBreakConfig().getHeadingBreakStrokeWidth();
if (headingBreakHeight >= 0) { if (headingBreakHeight >= 0) {
//noinspection SuspiciousNameCombination //noinspection SuspiciousNameCombination
paint.setStrokeWidth(headingBreakHeight); paint.setStrokeWidth(headingBreakHeight);
@ -441,8 +478,7 @@ public class SpannableTheme {
private int codeMultilineMargin; private int codeMultilineMargin;
private Typeface codeTypeface; private Typeface codeTypeface;
private int codeTextSize; private int codeTextSize;
private int headingBreakHeight = -1; private HeadingConfig headingConfig;
private int headingBreakColor;
private float scriptTextSizeRatio; private float scriptTextSizeRatio;
private int thematicBreakColor; private int thematicBreakColor;
private int thematicBreakHeight = -1; private int thematicBreakHeight = -1;
@ -468,8 +504,7 @@ public class SpannableTheme {
this.codeMultilineMargin = theme.codeMultilineMargin; this.codeMultilineMargin = theme.codeMultilineMargin;
this.codeTypeface = theme.codeTypeface; this.codeTypeface = theme.codeTypeface;
this.codeTextSize = theme.codeTextSize; this.codeTextSize = theme.codeTextSize;
this.headingBreakHeight = theme.headingBreakHeight; this.headingConfig = theme.headingConfig;
this.headingBreakColor = theme.headingBreakColor;
this.scriptTextSizeRatio = theme.scriptTextSizeRatio; this.scriptTextSizeRatio = theme.scriptTextSizeRatio;
this.thematicBreakColor = theme.thematicBreakColor; this.thematicBreakColor = theme.thematicBreakColor;
this.thematicBreakHeight = theme.thematicBreakHeight; this.thematicBreakHeight = theme.thematicBreakHeight;
@ -553,14 +588,9 @@ public class SpannableTheme {
} }
@NonNull @NonNull
public Builder headingBreakHeight(@Dimension int headingBreakHeight) { public Builder headingConfig(HeadingConfig headingConfig, float density) {
this.headingBreakHeight = headingBreakHeight; headingConfig.setDensityFactor(density);
return this; this.headingConfig = headingConfig;
}
@NonNull
public Builder headingBreakColor(@ColorInt int headingBreakColor) {
this.headingBreakColor = headingBreakColor;
return this; return this;
} }
@ -630,7 +660,7 @@ public class SpannableTheme {
private static class Dip { private static class Dip {
private final float density; protected final float density;
Dip(@NonNull Context context) { Dip(@NonNull Context context) {
this.density = context.getResources().getDisplayMetrics().density; this.density = context.getResources().getDisplayMetrics().density;

View File

@ -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
}

View File

@ -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;
}