Merge branch 'feature/extend_heading_formatting'
This commit is contained in:
commit
44a06e0a43
@ -36,4 +36,6 @@ dependencies {
|
||||
|
||||
compile 'com.google.dagger:dagger:2.10'
|
||||
annotationProcessor 'com.google.dagger:dagger-compiler:2.10'
|
||||
|
||||
implementation "com.android.support:appcompat-v7:27.0.2"
|
||||
}
|
||||
|
@ -1,11 +1,15 @@
|
||||
package ru.noties.markwon;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Typeface;
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.os.SystemClock;
|
||||
import android.support.annotation.FontRes;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.content.res.ResourcesCompat;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Future;
|
||||
@ -14,13 +18,14 @@ import javax.inject.Inject;
|
||||
|
||||
import ru.noties.debug.Debug;
|
||||
import ru.noties.markwon.spans.AsyncDrawable;
|
||||
import ru.noties.markwon.spans.SpannableTheme;
|
||||
import ru.noties.markwon.spans.heading.HeadingConfig;
|
||||
import ru.noties.markwon.spans.heading.HeadingTypeConfig;
|
||||
|
||||
@ActivityScope
|
||||
public class MarkdownRenderer {
|
||||
|
||||
interface MarkdownReadyListener {
|
||||
void onMarkdownReady(CharSequence markdown);
|
||||
}
|
||||
private final Context context;
|
||||
|
||||
@Inject
|
||||
AsyncDrawable.Loader loader;
|
||||
@ -34,14 +39,15 @@ public class MarkdownRenderer {
|
||||
private Future<?> task;
|
||||
|
||||
@Inject
|
||||
MarkdownRenderer() {
|
||||
MarkdownRenderer(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public void render(
|
||||
@NonNull final Context context,
|
||||
@Nullable final Uri uri,
|
||||
@NonNull final String markdown,
|
||||
@NonNull final MarkdownReadyListener listener) {
|
||||
@NonNull final Context context,
|
||||
@Nullable final Uri uri,
|
||||
@NonNull final String markdown,
|
||||
@NonNull final MarkdownReadyListener listener) {
|
||||
cancel();
|
||||
task = service.submit(new Runnable() {
|
||||
@Override
|
||||
@ -55,9 +61,10 @@ public class MarkdownRenderer {
|
||||
}
|
||||
|
||||
final SpannableConfiguration configuration = SpannableConfiguration.builder(context)
|
||||
.asyncDrawableLoader(loader)
|
||||
.urlProcessor(urlProcessor)
|
||||
.build();
|
||||
.theme(getSpannableTheme())
|
||||
.asyncDrawableLoader(loader)
|
||||
.urlProcessor(urlProcessor)
|
||||
.build();
|
||||
|
||||
final long start = SystemClock.uptimeMillis();
|
||||
|
||||
@ -92,4 +99,25 @@ public class MarkdownRenderer {
|
||||
private boolean isCancelled() {
|
||||
return task == null || task.isCancelled();
|
||||
}
|
||||
|
||||
private HeadingConfig getHeadingConfig() {
|
||||
final HeadingTypeConfig h1 = new HeadingTypeConfig(-1, Color.RED, getTypeface(R.font.opensans_semibold));
|
||||
final HeadingTypeConfig h2 = new HeadingTypeConfig(-1, Color.BLUE, getTypeface(R.font.opensans_regular));
|
||||
|
||||
return new HeadingConfig(h1, h2);
|
||||
}
|
||||
|
||||
private SpannableTheme getSpannableTheme() {
|
||||
return SpannableTheme.builderWithDefaults(context)
|
||||
.headingConfig(getHeadingConfig(), context.getResources().getDisplayMetrics().density)
|
||||
.build();
|
||||
}
|
||||
|
||||
private Typeface getTypeface(@FontRes int font){
|
||||
return ResourcesCompat.getFont(context, font);
|
||||
}
|
||||
|
||||
interface MarkdownReadyListener {
|
||||
void onMarkdownReady(CharSequence markdown);
|
||||
}
|
||||
}
|
||||
|
25
app/src/main/res/font/app_font.xml
Normal file
25
app/src/main/res/font/app_font.xml
Normal file
@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<font-family
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<!-- Regular -->
|
||||
<font
|
||||
android:font="@font/opensans_regular"
|
||||
android:fontStyle="normal"
|
||||
android:fontWeight="400"
|
||||
|
||||
app:font="@font/opensans_regular"
|
||||
app:fontStyle="normal"
|
||||
app:fontWeight="400"/>
|
||||
|
||||
<!-- Semi-bold -->
|
||||
<font
|
||||
android:font="@font/opensans_semibold"
|
||||
android:fontStyle="normal"
|
||||
android:fontWeight="700"
|
||||
|
||||
app:font="@font/opensans_semibold"
|
||||
app:fontStyle="normal"
|
||||
app:fontWeight="700"/>
|
||||
</font-family>
|
BIN
app/src/main/res/font/opensans_regular.ttf
Executable file
BIN
app/src/main/res/font/opensans_regular.ttf
Executable file
Binary file not shown.
BIN
app/src/main/res/font/opensans_semibold.ttf
Normal file
BIN
app/src/main/res/font/opensans_semibold.ttf
Normal file
Binary file not shown.
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
}
|
@ -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;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user