Introduced SpannableTheme
This commit is contained in:
parent
e50789bc40
commit
e0c10c658b
@ -18,4 +18,5 @@ dependencies {
|
|||||||
compile project(':library-renderer')
|
compile project(':library-renderer')
|
||||||
compile 'ru.noties:debug:3.0.0@jar'
|
compile 'ru.noties:debug:3.0.0@jar'
|
||||||
compile 'com.squareup.picasso:picasso:2.5.2'
|
compile 'com.squareup.picasso:picasso:2.5.2'
|
||||||
|
compile 'com.squareup.okhttp3:okhttp:3.8.0'
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,7 @@ import ru.noties.debug.Debug;
|
|||||||
import ru.noties.markwon.renderer.*;
|
import ru.noties.markwon.renderer.*;
|
||||||
import ru.noties.markwon.spans.AsyncDrawable;
|
import ru.noties.markwon.spans.AsyncDrawable;
|
||||||
import ru.noties.markwon.spans.CodeSpan;
|
import ru.noties.markwon.spans.CodeSpan;
|
||||||
import ru.noties.markwon.spans.DrawableSpanUtils;
|
import ru.noties.markwon.spans.AsyncDrawableSpanUtils;
|
||||||
|
|
||||||
public class MainActivity extends Activity {
|
public class MainActivity extends Activity {
|
||||||
|
|
||||||
@ -101,50 +101,52 @@ public class MainActivity extends Activity {
|
|||||||
.build();
|
.build();
|
||||||
final Node node = parser.parse(md);
|
final Node node = parser.parse(md);
|
||||||
|
|
||||||
final SpannableConfiguration configuration = SpannableConfiguration.builder(MainActivity.this)
|
// final SpannableConfiguration configuration = SpannableConfiguration.builder(MainActivity.this)
|
||||||
.setAsyncDrawableLoader(new AsyncDrawable.Loader() {
|
// .setAsyncDrawableLoader(new AsyncDrawable.Loader() {
|
||||||
@Override
|
// @Override
|
||||||
public void load(@NonNull String destination, @NonNull final AsyncDrawable drawable) {
|
// public void load(@NonNull String destination, @NonNull final AsyncDrawable drawable) {
|
||||||
Debug.i(destination);
|
// Debug.i(destination);
|
||||||
final Target target = new Target() {
|
// final Target target = new Target() {
|
||||||
@Override
|
// @Override
|
||||||
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
|
// public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
|
||||||
Debug.i();
|
// Debug.i();
|
||||||
final Drawable d = new BitmapDrawable(getResources(), bitmap);
|
// final Drawable d = new BitmapDrawable(getResources(), bitmap);
|
||||||
d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
|
// d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
|
||||||
drawable.setResult(d);
|
// drawable.setResult(d);
|
||||||
// textView.setText(textView.getText());
|
//// textView.setText(textView.getText());
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
|
// @Override
|
||||||
|
// public void onBitmapFailed(Drawable errorDrawable) {
|
||||||
|
// Debug.i();
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// @Override
|
||||||
|
// public void onPrepareLoad(Drawable placeHolderDrawable) {
|
||||||
|
// Debug.i();
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
// targets.add(target);
|
||||||
|
//
|
||||||
|
// picasso.load(destination)
|
||||||
|
// .tag(destination)
|
||||||
|
// .into(target);
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// @Override
|
||||||
|
// public void cancel(@NonNull String destination) {
|
||||||
|
// Debug.i(destination);
|
||||||
|
// picasso
|
||||||
|
// .cancelTag(destination);
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// .setCodeConfig(CodeSpan.Config.builder().setTextSize(
|
||||||
|
// (int) (getResources().getDisplayMetrics().density * 14 + .5F)
|
||||||
|
// ).setMultilineMargin((int) (getResources().getDisplayMetrics().density * 8 + .5F)).build())
|
||||||
|
// .build();
|
||||||
|
|
||||||
@Override
|
final SpannableConfiguration configuration = SpannableConfiguration.create(MainActivity.this);
|
||||||
public void onBitmapFailed(Drawable errorDrawable) {
|
|
||||||
Debug.i();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPrepareLoad(Drawable placeHolderDrawable) {
|
|
||||||
Debug.i();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
targets.add(target);
|
|
||||||
|
|
||||||
picasso.load(destination)
|
|
||||||
.tag(destination)
|
|
||||||
.into(target);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void cancel(@NonNull String destination) {
|
|
||||||
Debug.i(destination);
|
|
||||||
picasso
|
|
||||||
.cancelTag(destination);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.setCodeConfig(CodeSpan.Config.builder().setTextSize(
|
|
||||||
(int) (getResources().getDisplayMetrics().density * 14 + .5F)
|
|
||||||
).setMultilineMargin((int) (getResources().getDisplayMetrics().density * 8 + .5F)).build())
|
|
||||||
.build();
|
|
||||||
|
|
||||||
final CharSequence text = new ru.noties.markwon.renderer.SpannableRenderer().render(
|
final CharSequence text = new ru.noties.markwon.renderer.SpannableRenderer().render(
|
||||||
configuration,
|
configuration,
|
||||||
@ -168,7 +170,7 @@ public class MainActivity extends Activity {
|
|||||||
// NB! LinkMovementMethod forces frequent updates...
|
// NB! LinkMovementMethod forces frequent updates...
|
||||||
textView.setMovementMethod(LinkMovementMethod.getInstance());
|
textView.setMovementMethod(LinkMovementMethod.getInstance());
|
||||||
textView.setText(text);
|
textView.setText(text);
|
||||||
DrawableSpanUtils.scheduleDrawables(textView);
|
AsyncDrawableSpanUtils.scheduleDrawables(textView);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -53,8 +53,8 @@
|
|||||||
//import ru.noties.markwon.spans.EmphasisSpan;
|
//import ru.noties.markwon.spans.EmphasisSpan;
|
||||||
//import ru.noties.markwon.spans.BulletListItemSpan;
|
//import ru.noties.markwon.spans.BulletListItemSpan;
|
||||||
//import ru.noties.markwon.spans.StrongEmphasisSpan;
|
//import ru.noties.markwon.spans.StrongEmphasisSpan;
|
||||||
//import ru.noties.markwon.spans.SubSpan;
|
//import ru.noties.markwon.spans.SubScriptSpan;
|
||||||
//import ru.noties.markwon.spans.SupSpan;
|
//import ru.noties.markwon.spans.SuperScriptSpan;
|
||||||
//import ru.noties.markwon.spans.ThematicBreakSpan;
|
//import ru.noties.markwon.spans.ThematicBreakSpan;
|
||||||
//
|
//
|
||||||
//public class SpannableRenderer implements Renderer {
|
//public class SpannableRenderer implements Renderer {
|
||||||
@ -263,9 +263,9 @@
|
|||||||
// final int end = builder.length();
|
// final int end = builder.length();
|
||||||
// // here, additionally, we can render some tags ourselves (sup/sub)
|
// // here, additionally, we can render some tags ourselves (sup/sub)
|
||||||
// if ("sup".equals(item.tag)) {
|
// if ("sup".equals(item.tag)) {
|
||||||
// builder.setSpan(new SupSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
// builder.setSpan(new SuperScriptSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
// } else if("sub".equals(item.tag)) {
|
// } else if("sub".equals(item.tag)) {
|
||||||
// builder.setSpan(new SubSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
// builder.setSpan(new SubScriptSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
// } else if("del".equals(item.tag)) {
|
// } else if("del".equals(item.tag)) {
|
||||||
// // weird, but `Html` class does not return a spannable for `<del>o</del>`
|
// // weird, but `Html` class does not return a spannable for `<del>o</del>`
|
||||||
// // seems like a bug
|
// // seems like a bug
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
<TextView
|
||||||
android:id="@+id/activity_main"
|
android:id="@+id/activity_main"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<color name="colorPrimary">#3F51B5</color>
|
<color name="colorPrimary">#424242</color>
|
||||||
<color name="colorPrimaryDark">#303F9F</color>
|
<color name="colorPrimaryDark">#212121</color>
|
||||||
<color name="colorAccent">#FF4081</color>
|
<color name="colorAccent">#4caf50</color>
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -3,7 +3,7 @@ buildscript {
|
|||||||
jcenter()
|
jcenter()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:2.3.1'
|
classpath 'com.android.tools.build:gradle:2.3.2'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,8 +15,6 @@ android {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
||||||
compile project(':library-spans')
|
|
||||||
|
|
||||||
compile SUPPORT_ANNOTATIONS
|
compile SUPPORT_ANNOTATIONS
|
||||||
compile COMMON_MARK
|
compile COMMON_MARK
|
||||||
compile COMMON_MARK_STRIKETHROUGHT
|
compile COMMON_MARK_STRIKETHROUGHT
|
||||||
|
@ -0,0 +1,27 @@
|
|||||||
|
package ru.noties.markwon.renderer;
|
||||||
|
|
||||||
|
import android.content.ActivityNotFoundException;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.provider.Browser;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import ru.noties.markwon.spans.LinkSpan;
|
||||||
|
|
||||||
|
class LinkResolverDef implements LinkSpan.Resolver {
|
||||||
|
@Override
|
||||||
|
public void resolve(View view, @NonNull String link) {
|
||||||
|
final Uri uri = Uri.parse(link);
|
||||||
|
final Context context = view.getContext();
|
||||||
|
final Intent intent = new Intent(Intent.ACTION_VIEW, uri);
|
||||||
|
intent.putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName());
|
||||||
|
try {
|
||||||
|
context.startActivity(intent);
|
||||||
|
} catch (ActivityNotFoundException e) {
|
||||||
|
Log.w("LinkResolverDef", "Actvity was not found for intent, " + intent.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -4,12 +4,8 @@ import android.content.Context;
|
|||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
|
|
||||||
import ru.noties.markwon.spans.AsyncDrawable;
|
import ru.noties.markwon.spans.AsyncDrawable;
|
||||||
import ru.noties.markwon.spans.BlockQuoteSpan;
|
import ru.noties.markwon.spans.LinkSpan;
|
||||||
import ru.noties.markwon.spans.BulletListItemSpan;
|
import ru.noties.markwon.spans.SpannableTheme;
|
||||||
import ru.noties.markwon.spans.CodeSpan;
|
|
||||||
import ru.noties.markwon.spans.HeadingSpan;
|
|
||||||
import ru.noties.markwon.spans.OrderedListItemSpan;
|
|
||||||
import ru.noties.markwon.spans.ThematicBreakSpan;
|
|
||||||
|
|
||||||
public class SpannableConfiguration {
|
public class SpannableConfiguration {
|
||||||
|
|
||||||
@ -22,140 +18,81 @@ public class SpannableConfiguration {
|
|||||||
return new Builder(context);
|
return new Builder(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
private final BlockQuoteSpan.Config blockQuoteConfig;
|
private final SpannableTheme theme;
|
||||||
private final CodeSpan.Config codeConfig;
|
|
||||||
private final BulletListItemSpan.Config bulletListConfig;
|
|
||||||
private final HeadingSpan.Config headingConfig;
|
|
||||||
private final ThematicBreakSpan.Config thematicConfig;
|
|
||||||
private final OrderedListItemSpan.Config orderedListConfig;
|
|
||||||
private final AsyncDrawable.Loader asyncDrawableLoader;
|
private final AsyncDrawable.Loader asyncDrawableLoader;
|
||||||
|
private final SyntaxHighlight syntaxHighlight;
|
||||||
|
private final LinkSpan.Resolver linkResolver;
|
||||||
|
|
||||||
private SpannableConfiguration(Builder builder) {
|
private SpannableConfiguration(Builder builder) {
|
||||||
this.blockQuoteConfig = builder.blockQuoteConfig;
|
this.theme = builder.theme;
|
||||||
this.codeConfig = builder.codeConfig;
|
|
||||||
this.bulletListConfig = builder.bulletListConfig;
|
|
||||||
this.headingConfig = builder.headingConfig;
|
|
||||||
this.thematicConfig = builder.thematicConfig;
|
|
||||||
this.orderedListConfig = builder.orderedListConfig;
|
|
||||||
this.asyncDrawableLoader = builder.asyncDrawableLoader;
|
this.asyncDrawableLoader = builder.asyncDrawableLoader;
|
||||||
|
this.syntaxHighlight = builder.syntaxHighlight;
|
||||||
|
this.linkResolver = builder.linkResolver;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BlockQuoteSpan.Config getBlockQuoteConfig() {
|
public SpannableTheme theme() {
|
||||||
return blockQuoteConfig;
|
return theme;
|
||||||
}
|
}
|
||||||
|
|
||||||
public CodeSpan.Config getCodeConfig() {
|
public AsyncDrawable.Loader asyncDrawableLoader() {
|
||||||
return codeConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BulletListItemSpan.Config getBulletListConfig() {
|
|
||||||
return bulletListConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
public HeadingSpan.Config getHeadingConfig() {
|
|
||||||
return headingConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ThematicBreakSpan.Config getThematicConfig() {
|
|
||||||
return thematicConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
public OrderedListItemSpan.Config getOrderedListConfig() {
|
|
||||||
return orderedListConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AsyncDrawable.Loader getAsyncDrawableLoader() {
|
|
||||||
return asyncDrawableLoader;
|
return asyncDrawableLoader;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public SyntaxHighlight syntaxHighlight() {
|
||||||
|
return syntaxHighlight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LinkSpan.Resolver linkResolver() {
|
||||||
|
return linkResolver;
|
||||||
|
}
|
||||||
|
|
||||||
public static class Builder {
|
public static class Builder {
|
||||||
|
|
||||||
private final Context context;
|
private final Context context;
|
||||||
private BlockQuoteSpan.Config blockQuoteConfig;
|
private SpannableTheme theme;
|
||||||
private CodeSpan.Config codeConfig;
|
|
||||||
private BulletListItemSpan.Config bulletListConfig;
|
|
||||||
private HeadingSpan.Config headingConfig;
|
|
||||||
private ThematicBreakSpan.Config thematicConfig;
|
|
||||||
private OrderedListItemSpan.Config orderedListConfig;
|
|
||||||
private AsyncDrawable.Loader asyncDrawableLoader;
|
private AsyncDrawable.Loader asyncDrawableLoader;
|
||||||
|
private SyntaxHighlight syntaxHighlight;
|
||||||
|
private LinkSpan.Resolver linkResolver;
|
||||||
|
|
||||||
public Builder(Context context) {
|
public Builder(Context context) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder setBlockQuoteConfig(@NonNull BlockQuoteSpan.Config blockQuoteConfig) {
|
public Builder theme(SpannableTheme theme) {
|
||||||
this.blockQuoteConfig = blockQuoteConfig;
|
this.theme = theme;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder setCodeConfig(@NonNull CodeSpan.Config codeConfig) {
|
public Builder asyncDrawableLoader(AsyncDrawable.Loader asyncDrawableLoader) {
|
||||||
this.codeConfig = codeConfig;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder setBulletListConfig(@NonNull BulletListItemSpan.Config bulletListConfig) {
|
|
||||||
this.bulletListConfig = bulletListConfig;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder setHeadingConfig(@NonNull HeadingSpan.Config headingConfig) {
|
|
||||||
this.headingConfig = headingConfig;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder setThematicConfig(@NonNull ThematicBreakSpan.Config thematicConfig) {
|
|
||||||
this.thematicConfig = thematicConfig;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder setOrderedListConfig(@NonNull OrderedListItemSpan.Config orderedListConfig) {
|
|
||||||
this.orderedListConfig = orderedListConfig;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder setAsyncDrawableLoader(AsyncDrawable.Loader asyncDrawableLoader) {
|
|
||||||
this.asyncDrawableLoader = asyncDrawableLoader;
|
this.asyncDrawableLoader = asyncDrawableLoader;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo, change to something more reliable
|
public Builder syntaxHighlight(SyntaxHighlight syntaxHighlight) {
|
||||||
// todo, must mention that bullet/ordered/quote must have the same margin (or maybe we can just enforce it?)
|
this.syntaxHighlight = syntaxHighlight;
|
||||||
// actually it does make sense to have `blockQuote` & `list` margins (if they are different)
|
return this;
|
||||||
// todo, maybe move defaults to configuration classes?
|
}
|
||||||
|
|
||||||
|
public Builder linkResolver(LinkSpan.Resolver linkResolver) {
|
||||||
|
this.linkResolver = linkResolver;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public SpannableConfiguration build() {
|
public SpannableConfiguration build() {
|
||||||
if (blockQuoteConfig == null) {
|
if (theme == null) {
|
||||||
blockQuoteConfig = new BlockQuoteSpan.Config(
|
theme = SpannableTheme.create(context);
|
||||||
px(24),
|
|
||||||
0,
|
|
||||||
0
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (codeConfig == null) {
|
|
||||||
codeConfig = CodeSpan.Config.builder()
|
|
||||||
.setMultilineMargin(px(8))
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
if (bulletListConfig == null) {
|
|
||||||
bulletListConfig = new BulletListItemSpan.Config(px(24), 0, px(8), px(1));
|
|
||||||
}
|
|
||||||
if (headingConfig == null) {
|
|
||||||
headingConfig = new HeadingSpan.Config(px(1), 0);
|
|
||||||
}
|
|
||||||
if (thematicConfig == null) {
|
|
||||||
thematicConfig = new ThematicBreakSpan.Config(0, px(2));
|
|
||||||
}
|
|
||||||
if (orderedListConfig == null) {
|
|
||||||
orderedListConfig = new OrderedListItemSpan.Config(px(24), 0);
|
|
||||||
}
|
}
|
||||||
if (asyncDrawableLoader == null) {
|
if (asyncDrawableLoader == null) {
|
||||||
asyncDrawableLoader = new AsyncDrawableLoaderNoOp();
|
asyncDrawableLoader = new AsyncDrawableLoaderNoOp();
|
||||||
}
|
}
|
||||||
|
if (syntaxHighlight == null) {
|
||||||
|
syntaxHighlight = new SyntaxHighlightNoOp();
|
||||||
|
}
|
||||||
|
if (linkResolver == null) {
|
||||||
|
linkResolver = new LinkResolverDef();
|
||||||
|
}
|
||||||
return new SpannableConfiguration(this);
|
return new SpannableConfiguration(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private int px(int dp) {
|
|
||||||
return (int) (context.getResources().getDisplayMetrics().density * dp + .5F);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,6 @@ package ru.noties.markwon.renderer;
|
|||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.text.SpannableStringBuilder;
|
import android.text.SpannableStringBuilder;
|
||||||
import android.text.Spanned;
|
import android.text.Spanned;
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.text.style.StrikethroughSpan;
|
import android.text.style.StrikethroughSpan;
|
||||||
import android.text.style.URLSpan;
|
import android.text.style.URLSpan;
|
||||||
|
|
||||||
@ -38,6 +37,7 @@ import ru.noties.markwon.spans.BulletListItemSpan;
|
|||||||
import ru.noties.markwon.spans.CodeSpan;
|
import ru.noties.markwon.spans.CodeSpan;
|
||||||
import ru.noties.markwon.spans.EmphasisSpan;
|
import ru.noties.markwon.spans.EmphasisSpan;
|
||||||
import ru.noties.markwon.spans.HeadingSpan;
|
import ru.noties.markwon.spans.HeadingSpan;
|
||||||
|
import ru.noties.markwon.spans.LinkSpan;
|
||||||
import ru.noties.markwon.spans.OrderedListItemSpan;
|
import ru.noties.markwon.spans.OrderedListItemSpan;
|
||||||
import ru.noties.markwon.spans.StrongEmphasisSpan;
|
import ru.noties.markwon.spans.StrongEmphasisSpan;
|
||||||
import ru.noties.markwon.spans.ThematicBreakSpan;
|
import ru.noties.markwon.spans.ThematicBreakSpan;
|
||||||
@ -97,7 +97,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
|
|||||||
visitChildren(blockQuote);
|
visitChildren(blockQuote);
|
||||||
|
|
||||||
setSpan(length, new BlockQuoteSpan(
|
setSpan(length, new BlockQuoteSpan(
|
||||||
configuration.getBlockQuoteConfig(),
|
configuration.theme(),
|
||||||
blockQuoteIndent
|
blockQuoteIndent
|
||||||
));
|
));
|
||||||
|
|
||||||
@ -123,7 +123,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
|
|||||||
builder.append('\u00a0');
|
builder.append('\u00a0');
|
||||||
|
|
||||||
setSpan(length, new CodeSpan(
|
setSpan(length, new CodeSpan(
|
||||||
configuration.getCodeConfig(),
|
configuration.theme(),
|
||||||
false
|
false
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@ -131,17 +131,20 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
|
|||||||
@Override
|
@Override
|
||||||
public void visit(FencedCodeBlock fencedCodeBlock) {
|
public void visit(FencedCodeBlock fencedCodeBlock) {
|
||||||
|
|
||||||
// Debug.i(fencedCodeBlock);
|
|
||||||
|
|
||||||
newLine();
|
newLine();
|
||||||
|
|
||||||
final int length = builder.length();
|
final int length = builder.length();
|
||||||
|
|
||||||
|
// empty lines on top & bottom
|
||||||
builder.append('\u00a0').append('\n');
|
builder.append('\u00a0').append('\n');
|
||||||
builder.append(fencedCodeBlock.getLiteral());
|
builder.append(
|
||||||
|
configuration.syntaxHighlight()
|
||||||
|
.highlight(fencedCodeBlock.getInfo(), fencedCodeBlock.getLiteral())
|
||||||
|
);
|
||||||
builder.append('\u00a0').append('\n');
|
builder.append('\u00a0').append('\n');
|
||||||
|
|
||||||
setSpan(length, new CodeSpan(
|
setSpan(length, new CodeSpan(
|
||||||
configuration.getCodeConfig(),
|
configuration.theme(),
|
||||||
true
|
true
|
||||||
));
|
));
|
||||||
|
|
||||||
@ -189,7 +192,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
|
|||||||
visitChildren(listItem);
|
visitChildren(listItem);
|
||||||
|
|
||||||
setSpan(length, new OrderedListItemSpan(
|
setSpan(length, new OrderedListItemSpan(
|
||||||
configuration.getOrderedListConfig(),
|
configuration.theme(),
|
||||||
String.valueOf(start) + "." + '\u00a0',
|
String.valueOf(start) + "." + '\u00a0',
|
||||||
blockQuoteIndent,
|
blockQuoteIndent,
|
||||||
length
|
length
|
||||||
@ -204,7 +207,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
|
|||||||
visitChildren(listItem);
|
visitChildren(listItem);
|
||||||
|
|
||||||
setSpan(length, new BulletListItemSpan(
|
setSpan(length, new BulletListItemSpan(
|
||||||
configuration.getBulletListConfig(),
|
configuration.theme(),
|
||||||
blockQuoteIndent,
|
blockQuoteIndent,
|
||||||
listLevel - 1,
|
listLevel - 1,
|
||||||
length
|
length
|
||||||
@ -226,7 +229,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
|
|||||||
|
|
||||||
final int length = builder.length();
|
final int length = builder.length();
|
||||||
builder.append(' '); // without space it won't render
|
builder.append(' '); // without space it won't render
|
||||||
setSpan(length, new ThematicBreakSpan(configuration.getThematicConfig()));
|
setSpan(length, new ThematicBreakSpan(configuration.theme()));
|
||||||
|
|
||||||
newLine();
|
newLine();
|
||||||
builder.append('\n');
|
builder.append('\n');
|
||||||
@ -242,7 +245,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
|
|||||||
final int length = builder.length();
|
final int length = builder.length();
|
||||||
visitChildren(heading);
|
visitChildren(heading);
|
||||||
setSpan(length, new HeadingSpan(
|
setSpan(length, new HeadingSpan(
|
||||||
configuration.getHeadingConfig(),
|
configuration.theme(),
|
||||||
heading.getLevel(),
|
heading.getLevel(),
|
||||||
builder.length())
|
builder.length())
|
||||||
);
|
);
|
||||||
@ -315,7 +318,18 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
|
|||||||
builder.append(' '); // breakable space
|
builder.append(' '); // breakable space
|
||||||
}
|
}
|
||||||
|
|
||||||
setSpan(length, new AsyncDrawableSpan(new AsyncDrawable(image.getDestination(), configuration.getAsyncDrawableLoader())));
|
final Node parent = image.getParent();
|
||||||
|
final boolean link = parent != null && parent instanceof Link;
|
||||||
|
|
||||||
|
setSpan(length, new AsyncDrawableSpan(
|
||||||
|
configuration.theme(),
|
||||||
|
new AsyncDrawable(
|
||||||
|
image.getDestination(),
|
||||||
|
configuration.asyncDrawableLoader()
|
||||||
|
),
|
||||||
|
AsyncDrawableSpan.ALIGN_BOTTOM,
|
||||||
|
link)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -329,7 +343,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
|
|||||||
public void visit(Link link) {
|
public void visit(Link link) {
|
||||||
final int length = builder.length();
|
final int length = builder.length();
|
||||||
visitChildren(link);
|
visitChildren(link);
|
||||||
setSpan(length, new URLSpan(link.getDestination()));
|
setSpan(length, new LinkSpan(configuration.theme(), link.getDestination(), configuration.linkResolver()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setSpan(int start, @NonNull Object span) {
|
private void setSpan(int start, @NonNull Object span) {
|
||||||
|
@ -14,6 +14,7 @@ public class SpannableRenderer {
|
|||||||
// * Common interface for images (in markdown & inline-html)
|
// * Common interface for images (in markdown & inline-html)
|
||||||
// * util method to properly copy markdown (with lists/links, etc)
|
// * util method to properly copy markdown (with lists/links, etc)
|
||||||
// * util to apply empty line height
|
// * util to apply empty line height
|
||||||
|
// * transform relative urls to absolute ones...
|
||||||
|
|
||||||
public CharSequence render(@NonNull SpannableConfiguration configuration, @Nullable Node node) {
|
public CharSequence render(@NonNull SpannableConfiguration configuration, @Nullable Node node) {
|
||||||
final CharSequence out;
|
final CharSequence out;
|
||||||
|
@ -0,0 +1,10 @@
|
|||||||
|
package ru.noties.markwon.renderer;
|
||||||
|
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
|
public interface SyntaxHighlight {
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
CharSequence highlight(@Nullable String info, @NonNull String code);
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
package ru.noties.markwon.renderer;
|
||||||
|
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
|
class SyntaxHighlightNoOp implements SyntaxHighlight {
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public CharSequence highlight(@Nullable String info, @NonNull String code) {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
}
|
@ -3,7 +3,6 @@ package ru.noties.markwon.spans;
|
|||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
import android.graphics.Paint;
|
import android.graphics.Paint;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.graphics.drawable.Drawable;
|
|
||||||
import android.support.annotation.IntDef;
|
import android.support.annotation.IntDef;
|
||||||
import android.support.annotation.IntRange;
|
import android.support.annotation.IntRange;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
@ -14,22 +13,38 @@ import android.text.style.ReplacementSpan;
|
|||||||
public class AsyncDrawableSpan extends ReplacementSpan {
|
public class AsyncDrawableSpan extends ReplacementSpan {
|
||||||
|
|
||||||
@IntDef({ALIGN_BOTTOM, ALIGN_BASELINE, ALIGN_CENTER})
|
@IntDef({ALIGN_BOTTOM, ALIGN_BASELINE, ALIGN_CENTER})
|
||||||
@interface Alignment {}
|
@interface Alignment {
|
||||||
|
}
|
||||||
|
|
||||||
public static final int ALIGN_BOTTOM = 0;
|
public static final int ALIGN_BOTTOM = 0;
|
||||||
public static final int ALIGN_BASELINE = 1;
|
public static final int ALIGN_BASELINE = 1;
|
||||||
public static final int ALIGN_CENTER = 2; // will only center if drawable height is less than text line height
|
public static final int ALIGN_CENTER = 2; // will only center if drawable height is less than text line height
|
||||||
|
|
||||||
|
private final SpannableTheme theme;
|
||||||
private final AsyncDrawable drawable;
|
private final AsyncDrawable drawable;
|
||||||
private final int alignment;
|
private final int alignment;
|
||||||
|
private final boolean replacementTextIsLink;
|
||||||
|
|
||||||
public AsyncDrawableSpan(@NonNull AsyncDrawable drawable) {
|
public AsyncDrawableSpan(@NonNull SpannableTheme theme, @NonNull AsyncDrawable drawable) {
|
||||||
this(drawable, ALIGN_BOTTOM);
|
this(theme, drawable, ALIGN_BOTTOM);
|
||||||
}
|
}
|
||||||
|
|
||||||
public AsyncDrawableSpan(@NonNull AsyncDrawable drawable, @Alignment int alignment) {
|
public AsyncDrawableSpan(
|
||||||
|
@NonNull SpannableTheme theme,
|
||||||
|
@NonNull AsyncDrawable drawable,
|
||||||
|
@Alignment int alignment) {
|
||||||
|
this(theme, drawable, alignment, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AsyncDrawableSpan(
|
||||||
|
@NonNull SpannableTheme theme,
|
||||||
|
@NonNull AsyncDrawable drawable,
|
||||||
|
@Alignment int alignment,
|
||||||
|
boolean replacementTextIsLink) {
|
||||||
|
this.theme = theme;
|
||||||
this.drawable = drawable;
|
this.drawable = drawable;
|
||||||
this.alignment = alignment;
|
this.alignment = alignment;
|
||||||
|
this.replacementTextIsLink = replacementTextIsLink;
|
||||||
|
|
||||||
// additionally set intrinsic bounds if empty
|
// additionally set intrinsic bounds if empty
|
||||||
final Rect rect = drawable.getBounds();
|
final Rect rect = drawable.getBounds();
|
||||||
@ -66,8 +81,13 @@ public class AsyncDrawableSpan extends ReplacementSpan {
|
|||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
size = (int) (paint.measureText(text, start, end) + .5F);
|
// we will apply style here in case if theme modifies textSize or style (affects metrics)
|
||||||
|
if (replacementTextIsLink) {
|
||||||
|
theme.applyLinkStyle(paint);
|
||||||
|
}
|
||||||
|
|
||||||
|
// NB, no specific text handling (no new lines, etc)
|
||||||
|
size = (int) (paint.measureText(text, start, end) + .5F);
|
||||||
}
|
}
|
||||||
|
|
||||||
return size;
|
return size;
|
||||||
@ -108,7 +128,15 @@ public class AsyncDrawableSpan extends ReplacementSpan {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
final int textY = (int) (bottom - ((bottom - top) / 2) - ((paint.descent() + paint.ascent()) / 2.F + .5F));
|
// will it make sense to have additional background/borders for an image replacement?
|
||||||
|
// let's focus on main functionality and then think of it
|
||||||
|
|
||||||
|
final float textY = CanvasUtils.textCenterY(top, bottom, paint);
|
||||||
|
if (replacementTextIsLink) {
|
||||||
|
theme.applyLinkStyle(paint);
|
||||||
|
}
|
||||||
|
|
||||||
|
// NB, no specific text handling (no new lines, etc)
|
||||||
canvas.drawText(text, start, end, x, textY, paint);
|
canvas.drawText(text, start, end, x, textY, paint);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -11,7 +11,7 @@ import android.widget.TextView;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class DrawableSpanUtils {
|
public class AsyncDrawableSpanUtils {
|
||||||
|
|
||||||
// todo, add `unschedule` method (to be used when new text is set, so
|
// todo, add `unschedule` method (to be used when new text is set, so
|
||||||
// drawables are removed from callbacks)
|
// drawables are removed from callbacks)
|
||||||
@ -66,7 +66,7 @@ public class DrawableSpanUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private DrawableSpanUtils() {}
|
private AsyncDrawableSpanUtils() {}
|
||||||
|
|
||||||
private static class DrawableCallbackImpl implements Drawable.Callback {
|
private static class DrawableCallbackImpl implements Drawable.Callback {
|
||||||
|
|
@ -0,0 +1,51 @@
|
|||||||
|
package ru.noties.markwon.spans;
|
||||||
|
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.text.Layout;
|
||||||
|
import android.text.style.LeadingMarginSpan;
|
||||||
|
|
||||||
|
public class BlockQuoteSpan implements LeadingMarginSpan {
|
||||||
|
|
||||||
|
private final SpannableTheme theme;
|
||||||
|
private final Rect rect = ObjectsPool.rect();
|
||||||
|
private final Paint paint = ObjectsPool.paint();
|
||||||
|
private final int indent;
|
||||||
|
|
||||||
|
public BlockQuoteSpan(@NonNull SpannableTheme theme, int indent) {
|
||||||
|
this.theme = theme;
|
||||||
|
this.indent = indent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getLeadingMargin(boolean first) {
|
||||||
|
return theme.getBlockMargin();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void drawLeadingMargin(
|
||||||
|
Canvas c,
|
||||||
|
Paint p,
|
||||||
|
int x,
|
||||||
|
int dir,
|
||||||
|
int top,
|
||||||
|
int baseline,
|
||||||
|
int bottom,
|
||||||
|
CharSequence text,
|
||||||
|
int start,
|
||||||
|
int end,
|
||||||
|
boolean first,
|
||||||
|
Layout layout) {
|
||||||
|
|
||||||
|
final int width = theme.getBlockQuoteWidth();
|
||||||
|
|
||||||
|
theme.applyBlockQuoteStyle(paint);
|
||||||
|
|
||||||
|
final int left = theme.getBlockMargin() * (indent - 1);
|
||||||
|
rect.set(left, top, left + width, bottom);
|
||||||
|
|
||||||
|
c.drawRect(rect, paint);
|
||||||
|
}
|
||||||
|
}
|
@ -4,7 +4,6 @@ import android.graphics.Canvas;
|
|||||||
import android.graphics.Paint;
|
import android.graphics.Paint;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.graphics.RectF;
|
import android.graphics.RectF;
|
||||||
import android.support.annotation.ColorInt;
|
|
||||||
import android.support.annotation.IntRange;
|
import android.support.annotation.IntRange;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.text.Layout;
|
import android.text.Layout;
|
||||||
@ -12,30 +11,7 @@ import android.text.style.LeadingMarginSpan;
|
|||||||
|
|
||||||
public class BulletListItemSpan implements LeadingMarginSpan {
|
public class BulletListItemSpan implements LeadingMarginSpan {
|
||||||
|
|
||||||
// todo, there are 3 types of bullets: filled circle, stroke circle & filled rectangle
|
private SpannableTheme theme;
|
||||||
// also, there are ordered lists
|
|
||||||
|
|
||||||
public static class Config {
|
|
||||||
|
|
||||||
final int marginWidth;
|
|
||||||
final int bulletColor; // by default uses text color
|
|
||||||
final int bulletSide;
|
|
||||||
final int bulletStrokeWidth;
|
|
||||||
|
|
||||||
// from 0 but it makes sense to provide something wider
|
|
||||||
public Config(
|
|
||||||
@IntRange(from = 0) int marginWidth,
|
|
||||||
@ColorInt int bulletColor,
|
|
||||||
@IntRange(from = 0) int bulletSide,
|
|
||||||
@IntRange(from = 0) int bulletStrokeWidth) {
|
|
||||||
this.marginWidth = marginWidth;
|
|
||||||
this.bulletColor = bulletColor;
|
|
||||||
this.bulletSide = bulletSide;
|
|
||||||
this.bulletStrokeWidth = bulletStrokeWidth;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final Config config;
|
|
||||||
|
|
||||||
private final Paint paint = ObjectsPool.paint();
|
private final Paint paint = ObjectsPool.paint();
|
||||||
private final RectF circle = ObjectsPool.rectF();
|
private final RectF circle = ObjectsPool.rectF();
|
||||||
@ -46,11 +22,11 @@ public class BulletListItemSpan implements LeadingMarginSpan {
|
|||||||
private final int start;
|
private final int start;
|
||||||
|
|
||||||
public BulletListItemSpan(
|
public BulletListItemSpan(
|
||||||
@NonNull Config config,
|
@NonNull SpannableTheme theme,
|
||||||
@IntRange(from = 0) int blockIndent,
|
@IntRange(from = 0) int blockIndent,
|
||||||
@IntRange(from = 0) int level,
|
@IntRange(from = 0) int level,
|
||||||
@IntRange(from = 0) int start) {
|
@IntRange(from = 0) int start) {
|
||||||
this.config = config;
|
this.theme = theme;
|
||||||
this.blockIndent = blockIndent;
|
this.blockIndent = blockIndent;
|
||||||
this.level = level;
|
this.level = level;
|
||||||
this.start = start;
|
this.start = start;
|
||||||
@ -58,7 +34,7 @@ public class BulletListItemSpan implements LeadingMarginSpan {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getLeadingMargin(boolean first) {
|
public int getLeadingMargin(boolean first) {
|
||||||
return config.marginWidth;
|
return theme.getBlockMargin();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -69,39 +45,17 @@ public class BulletListItemSpan implements LeadingMarginSpan {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final int color;
|
paint.set(p);
|
||||||
final float stroke;
|
|
||||||
|
|
||||||
if (config.bulletColor == 0) {
|
theme.applyListItemStyle(paint);
|
||||||
color = p.getColor();
|
|
||||||
} else {
|
|
||||||
color = config.bulletColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.bulletStrokeWidth == 0) {
|
|
||||||
stroke = p.getStrokeWidth();
|
|
||||||
} else {
|
|
||||||
stroke = config.bulletStrokeWidth;
|
|
||||||
}
|
|
||||||
|
|
||||||
paint.setColor(color);
|
|
||||||
paint.setStrokeWidth(stroke);
|
|
||||||
|
|
||||||
final int save = c.save();
|
final int save = c.save();
|
||||||
try {
|
try {
|
||||||
|
|
||||||
// by default we use half of margin width, but if height is less than width, we calculate from it
|
final int width = theme.getBlockMargin();
|
||||||
final int width = config.marginWidth;
|
|
||||||
final int height = bottom - top;
|
final int height = bottom - top;
|
||||||
|
|
||||||
final int min = Math.min(config.marginWidth, height) / 2;
|
final int side = theme.getBulletWidth(bottom - top);
|
||||||
final int side;
|
|
||||||
if (config.bulletSide == 0
|
|
||||||
|| config.bulletSide > min) {
|
|
||||||
side = min;
|
|
||||||
} else {
|
|
||||||
side = config.bulletSide;
|
|
||||||
}
|
|
||||||
|
|
||||||
final int marginLeft = (width - side) / 2;
|
final int marginLeft = (width - side) / 2;
|
||||||
final int marginTop = (height - side) / 2;
|
final int marginTop = (height - side) / 2;
|
@ -0,0 +1,14 @@
|
|||||||
|
package ru.noties.markwon.spans;
|
||||||
|
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
|
||||||
|
abstract class CanvasUtils {
|
||||||
|
|
||||||
|
static float textCenterY(int top, int bottom, @NonNull Paint paint) {
|
||||||
|
return (int) (bottom - ((bottom - top) / 2) - ((paint.descent() + paint.ascent()) / 2.F + .5F));
|
||||||
|
}
|
||||||
|
|
||||||
|
private CanvasUtils() {
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,60 @@
|
|||||||
|
package ru.noties.markwon.spans;
|
||||||
|
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.text.Layout;
|
||||||
|
import android.text.TextPaint;
|
||||||
|
import android.text.style.LeadingMarginSpan;
|
||||||
|
import android.text.style.MetricAffectingSpan;
|
||||||
|
|
||||||
|
public class CodeSpan extends MetricAffectingSpan implements LeadingMarginSpan {
|
||||||
|
|
||||||
|
private final SpannableTheme theme;
|
||||||
|
private final Rect rect = ObjectsPool.rect();
|
||||||
|
private final Paint paint = ObjectsPool.paint();
|
||||||
|
|
||||||
|
private final boolean multiline;
|
||||||
|
|
||||||
|
public CodeSpan(@NonNull SpannableTheme theme, boolean multiline) {
|
||||||
|
this.theme = theme;
|
||||||
|
this.multiline = multiline;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateMeasureState(TextPaint p) {
|
||||||
|
apply(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateDrawState(TextPaint ds) {
|
||||||
|
apply(ds);
|
||||||
|
if (!multiline) {
|
||||||
|
ds.bgColor = theme.getCodeBackgroundColor(ds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void apply(TextPaint p) {
|
||||||
|
theme.applyCodeTextStyle(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getLeadingMargin(boolean first) {
|
||||||
|
return multiline ? theme.getCodeMultilineMargin() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void drawLeadingMargin(Canvas c, Paint p, int x, int dir, int top, int baseline, int bottom, CharSequence text, int start, int end, boolean first, Layout layout) {
|
||||||
|
|
||||||
|
if (multiline) {
|
||||||
|
|
||||||
|
paint.setStyle(Paint.Style.FILL);
|
||||||
|
paint.setColor(theme.getCodeBackgroundColor(p));
|
||||||
|
|
||||||
|
rect.set(x, top, c.getWidth(), bottom);
|
||||||
|
|
||||||
|
c.drawRect(rect, paint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
package ru.noties.markwon.spans;
|
package ru.noties.markwon.spans;
|
||||||
|
|
||||||
class ColorUtils {
|
abstract class ColorUtils {
|
||||||
|
|
||||||
static int applyAlpha(int color, int alpha) {
|
static int applyAlpha(int color, int alpha) {
|
||||||
return (color & 0x00FFFFFF) | (alpha << 24);
|
return (color & 0x00FFFFFF) | (alpha << 24);
|
@ -0,0 +1,67 @@
|
|||||||
|
package ru.noties.markwon.spans;
|
||||||
|
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.support.annotation.IntRange;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.text.Layout;
|
||||||
|
import android.text.TextPaint;
|
||||||
|
import android.text.style.LeadingMarginSpan;
|
||||||
|
import android.text.style.MetricAffectingSpan;
|
||||||
|
|
||||||
|
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;
|
||||||
|
private final int end;
|
||||||
|
|
||||||
|
public HeadingSpan(@NonNull SpannableTheme theme, @IntRange(from = 1, to = 6) int level, @IntRange(from = 0) int end) {
|
||||||
|
this.theme = theme;
|
||||||
|
this.level = level;
|
||||||
|
this.end = end;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateMeasureState(TextPaint p) {
|
||||||
|
apply(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateDrawState(TextPaint tp) {
|
||||||
|
apply(tp);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void apply(TextPaint paint) {
|
||||||
|
theme.applyHeadingTextStyle(paint, level);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getLeadingMargin(boolean first) {
|
||||||
|
// no margin actually, but we need to access Canvas to draw break
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void drawLeadingMargin(Canvas c, Paint p, int x, int dir, int top, int baseline, int bottom, CharSequence text, int start, int end, boolean first, Layout layout) {
|
||||||
|
|
||||||
|
if (level == 1
|
||||||
|
|| level == 2) {
|
||||||
|
|
||||||
|
if (this.end == end) {
|
||||||
|
|
||||||
|
paint.set(p);
|
||||||
|
|
||||||
|
theme.applyHeadingBreakStyle(paint);
|
||||||
|
|
||||||
|
final float height = paint.getStrokeWidth();
|
||||||
|
final int b = (int) (bottom - height + .5F);
|
||||||
|
|
||||||
|
rect.set(x, b, c.getWidth(), bottom);
|
||||||
|
c.drawRect(rect, paint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
package ru.noties.markwon.spans;
|
||||||
|
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.text.TextPaint;
|
||||||
|
import android.text.style.ClickableSpan;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
public class LinkSpan extends ClickableSpan {
|
||||||
|
|
||||||
|
public interface Resolver {
|
||||||
|
void resolve(View view, @NonNull String link);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final SpannableTheme theme;
|
||||||
|
private final String link;
|
||||||
|
private final Resolver resolver;
|
||||||
|
|
||||||
|
public LinkSpan(@NonNull SpannableTheme theme, @NonNull String link, @NonNull Resolver resolver) {
|
||||||
|
this.theme = theme;
|
||||||
|
this.link = link;
|
||||||
|
this.resolver = resolver;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick(View widget) {
|
||||||
|
resolver.resolve(widget, link);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateDrawState(TextPaint ds) {
|
||||||
|
theme.applyLinkStyle(ds);
|
||||||
|
}
|
||||||
|
}
|
@ -2,7 +2,6 @@ package ru.noties.markwon.spans;
|
|||||||
|
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
import android.graphics.Paint;
|
import android.graphics.Paint;
|
||||||
import android.support.annotation.ColorInt;
|
|
||||||
import android.support.annotation.IntRange;
|
import android.support.annotation.IntRange;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.text.Layout;
|
import android.text.Layout;
|
||||||
@ -10,29 +9,18 @@ import android.text.style.LeadingMarginSpan;
|
|||||||
|
|
||||||
public class OrderedListItemSpan implements LeadingMarginSpan {
|
public class OrderedListItemSpan implements LeadingMarginSpan {
|
||||||
|
|
||||||
public static class Config {
|
private final SpannableTheme theme;
|
||||||
|
|
||||||
final int marginWidth; // by default 0
|
|
||||||
final int numberColor; // by default color of the main text
|
|
||||||
|
|
||||||
public Config(@IntRange(from = 0) int marginWidth, @ColorInt int numberColor) {
|
|
||||||
this.marginWidth = marginWidth;
|
|
||||||
this.numberColor = numberColor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final Config config;
|
|
||||||
private final String number;
|
private final String number;
|
||||||
private final int blockIndent;
|
private final int blockIndent;
|
||||||
private final int start;
|
private final int start;
|
||||||
|
|
||||||
public OrderedListItemSpan(
|
public OrderedListItemSpan(
|
||||||
@NonNull Config config,
|
@NonNull SpannableTheme theme,
|
||||||
@NonNull String number,
|
@NonNull String number,
|
||||||
@IntRange(from = 0) int blockIndent,
|
@IntRange(from = 0) int blockIndent,
|
||||||
@IntRange(from = 0) int start
|
@IntRange(from = 0) int start
|
||||||
) {
|
) {
|
||||||
this.config = config;
|
this.theme = theme;
|
||||||
this.number = number;
|
this.number = number;
|
||||||
this.blockIndent = blockIndent;
|
this.blockIndent = blockIndent;
|
||||||
this.start = start;
|
this.start = start;
|
||||||
@ -40,7 +28,7 @@ public class OrderedListItemSpan implements LeadingMarginSpan {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getLeadingMargin(boolean first) {
|
public int getLeadingMargin(boolean first) {
|
||||||
return config.marginWidth;
|
return theme.getBlockMargin();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -51,15 +39,13 @@ public class OrderedListItemSpan implements LeadingMarginSpan {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.numberColor != 0) {
|
theme.applyListItemStyle(p);
|
||||||
p.setColor(config.numberColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
final int width = config.marginWidth;
|
final int width = theme.getBlockMargin();
|
||||||
final int numberWidth = (int) (p.measureText(number) + .5F);
|
final int numberWidth = (int) (p.measureText(number) + .5F);
|
||||||
final int numberX = (width * blockIndent) - numberWidth;
|
final int numberX = (width * blockIndent) - numberWidth;
|
||||||
|
|
||||||
final int numberY = bottom - ((bottom - top) / 2) - (int) ((p.descent() + p.ascent()) / 2);
|
final float numberY = CanvasUtils.textCenterY(top, bottom, p);
|
||||||
|
|
||||||
c.drawText(number, numberX, numberY, p);
|
c.drawText(number, numberX, numberY, p);
|
||||||
}
|
}
|
@ -0,0 +1,456 @@
|
|||||||
|
package ru.noties.markwon.spans;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.TypedArray;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.Typeface;
|
||||||
|
import android.support.annotation.AttrRes;
|
||||||
|
import android.support.annotation.IntRange;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.text.TextPaint;
|
||||||
|
import android.util.TypedValue;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
@SuppressWarnings("WeakerAccess")
|
||||||
|
public class SpannableTheme {
|
||||||
|
|
||||||
|
// this method should be used if TextView is known beforehand
|
||||||
|
// it will correctly measure the `space` char and set it as `codeMultilineMargin`
|
||||||
|
// otherwise this value must be set explicitly (
|
||||||
|
public static SpannableTheme create(@NonNull TextView textView) {
|
||||||
|
return builderWithDefaults(textView.getContext())
|
||||||
|
.codeMultilineMargin((int) (textView.getPaint().measureText("\u00a0") + .5F))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
// this create default theme (except for `codeMultilineMargin` property)
|
||||||
|
public static SpannableTheme create(@NonNull Context context) {
|
||||||
|
return builderWithDefaults(context).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Builder builder() {
|
||||||
|
return new Builder();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Builder builder(@NonNull SpannableTheme copyFrom) {
|
||||||
|
return new Builder(copyFrom);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Builder builderWithDefaults(@NonNull Context context) {
|
||||||
|
final Px px = new Px(context);
|
||||||
|
return new Builder()
|
||||||
|
.linkColor(resolve(context, android.R.attr.textColorLink))
|
||||||
|
.codeMultilineMargin(px.px(8))
|
||||||
|
.blockMargin(px.px(24))
|
||||||
|
.bulletListItemStrokeWidth(px.px(1))
|
||||||
|
.headingBreakHeight(px.px(1))
|
||||||
|
.thematicBreakHeight(px.px(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int resolve(Context context, @AttrRes int attr) {
|
||||||
|
final TypedValue typedValue = new TypedValue();
|
||||||
|
final int attrs[] = new int[]{attr};
|
||||||
|
final TypedArray typedArray = context.obtainStyledAttributes(typedValue.data, attrs);
|
||||||
|
try {
|
||||||
|
return typedArray.getColor(0, 0);
|
||||||
|
} finally {
|
||||||
|
typedArray.recycle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static final int BLOCK_QUOTE_DEF_COLOR_ALPHA = 50;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
};
|
||||||
|
|
||||||
|
protected static final float SCRIPT_DEF_TEXT_SIZE_RATIO = .75F;
|
||||||
|
|
||||||
|
protected static final int THEMATIC_BREAK_DEF_ALPHA = 75;
|
||||||
|
|
||||||
|
protected final int linkColor;
|
||||||
|
|
||||||
|
// used in quote, lists
|
||||||
|
protected final int blockMargin;
|
||||||
|
|
||||||
|
// by default it's 1/4th of `blockMargin`
|
||||||
|
protected final int blockQuoteWidth;
|
||||||
|
|
||||||
|
// by default it's text color with `BLOCK_QUOTE_DEF_COLOR_ALPHA` applied alpha
|
||||||
|
protected final int blockQuoteColor;
|
||||||
|
|
||||||
|
// by default uses text color (applied for un-ordered lists & ordered (bullets & numbers)
|
||||||
|
protected final int listItemColor;
|
||||||
|
|
||||||
|
// by default the stroke color of a paint object
|
||||||
|
protected final int bulletListItemStrokeWidth;
|
||||||
|
|
||||||
|
// width of bullet, by default min(blockMargin, height) / 2
|
||||||
|
protected final int bulletWidth;
|
||||||
|
|
||||||
|
// by default - main text color
|
||||||
|
protected final int codeTextColor;
|
||||||
|
|
||||||
|
// by default 0.1 alpha of textColor/codeTextColor
|
||||||
|
protected final int codeBackgroundColor;
|
||||||
|
|
||||||
|
// by default `width` of a space char... it's fun and games, but span doesn't have access to paint in `getLeadingMargin`
|
||||||
|
// so, we need to set this value explicitly (think of an utility method, that takes TextView/TextPaint and measures space char)
|
||||||
|
protected final int codeMultilineMargin;
|
||||||
|
|
||||||
|
// by default Typeface.MONOSPACE
|
||||||
|
protected final Typeface codeTypeface;
|
||||||
|
|
||||||
|
// by default a bit (how much?!) smaller than normal text
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
// by default `SCRIPT_DEF_TEXT_SIZE_RATIO`
|
||||||
|
protected final float scriptTextSizeRatio;
|
||||||
|
|
||||||
|
// by default textColor with `THEMATIC_BREAK_DEF_ALPHA` applied alpha
|
||||||
|
protected final int thematicBreakColor;
|
||||||
|
|
||||||
|
// by default paint.strokeWidth
|
||||||
|
protected final int thematicBreakHeight;
|
||||||
|
|
||||||
|
protected SpannableTheme(@NonNull Builder builder) {
|
||||||
|
this.linkColor = builder.linkColor;
|
||||||
|
this.blockMargin = builder.blockMargin;
|
||||||
|
this.blockQuoteWidth = builder.blockQuoteWidth;
|
||||||
|
this.blockQuoteColor = builder.blockQuoteColor;
|
||||||
|
this.listItemColor = builder.listItemColor;
|
||||||
|
this.bulletListItemStrokeWidth = builder.bulletListItemStrokeWidth;
|
||||||
|
this.bulletWidth = builder.bulletWidth;
|
||||||
|
this.codeTextColor = builder.codeTextColor;
|
||||||
|
this.codeBackgroundColor = builder.codeBackgroundColor;
|
||||||
|
this.codeMultilineMargin = builder.codeMultilineMargin;
|
||||||
|
this.codeTypeface = builder.codeTypeface;
|
||||||
|
this.codeTextSize = builder.codeTextSize;
|
||||||
|
this.headingBreakHeight = builder.headingBreakHeight;
|
||||||
|
this.headingBreakColor = builder.headingBreakColor;
|
||||||
|
this.scriptTextSizeRatio = builder.scriptTextSizeRatio;
|
||||||
|
this.thematicBreakColor = builder.thematicBreakColor;
|
||||||
|
this.thematicBreakHeight = builder.thematicBreakHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void applyLinkStyle(@NonNull Paint paint) {
|
||||||
|
paint.setUnderlineText(true);
|
||||||
|
if (linkColor != 0) {
|
||||||
|
// by default we will be using text color
|
||||||
|
paint.setColor(linkColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void applyBlockQuoteStyle(@NonNull Paint paint) {
|
||||||
|
final int color;
|
||||||
|
if (blockQuoteColor == 0) {
|
||||||
|
color = ColorUtils.applyAlpha(paint.getColor(), BLOCK_QUOTE_DEF_COLOR_ALPHA);
|
||||||
|
} else {
|
||||||
|
color = blockQuoteColor;
|
||||||
|
}
|
||||||
|
paint.setStyle(Paint.Style.FILL);
|
||||||
|
paint.setColor(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getBlockMargin() {
|
||||||
|
return blockMargin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getBlockQuoteWidth() {
|
||||||
|
final int out;
|
||||||
|
if (blockQuoteWidth == 0) {
|
||||||
|
out = (int) (blockMargin * .25F + .5F);
|
||||||
|
} else {
|
||||||
|
out = blockQuoteWidth;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void applyListItemStyle(@NonNull Paint paint) {
|
||||||
|
|
||||||
|
final int color;
|
||||||
|
if (listItemColor != 0) {
|
||||||
|
color = listItemColor;
|
||||||
|
} else {
|
||||||
|
color = paint.getColor();
|
||||||
|
}
|
||||||
|
paint.setColor(color);
|
||||||
|
|
||||||
|
if (bulletListItemStrokeWidth != 0) {
|
||||||
|
paint.setStrokeWidth(bulletListItemStrokeWidth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getBulletWidth(int height) {
|
||||||
|
|
||||||
|
final int min = Math.min(blockMargin, height) / 2;
|
||||||
|
|
||||||
|
final int width;
|
||||||
|
if (bulletWidth == 0
|
||||||
|
|| bulletWidth > min) {
|
||||||
|
width = min;
|
||||||
|
} else {
|
||||||
|
width = bulletWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
return width;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void applyCodeTextStyle(@NonNull Paint paint) {
|
||||||
|
|
||||||
|
if (codeTextColor != 0) {
|
||||||
|
paint.setColor(codeTextColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
// custom typeface was set
|
||||||
|
if (codeTypeface != null) {
|
||||||
|
paint.setTypeface(codeTypeface);
|
||||||
|
if (codeTextSize != 0) {
|
||||||
|
paint.setTextSize(codeTextSize);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
paint.setTypeface(Typeface.MONOSPACE);
|
||||||
|
final float textSize;
|
||||||
|
if (codeTextSize != 0) {
|
||||||
|
textSize = codeTextSize;
|
||||||
|
} else {
|
||||||
|
textSize = paint.getTextSize() * CODE_DEF_TEXT_SIZE_RATIO;
|
||||||
|
}
|
||||||
|
paint.setTextSize(textSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCodeMultilineMargin() {
|
||||||
|
return codeMultilineMargin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCodeBackgroundColor(@NonNull Paint paint) {
|
||||||
|
final int color;
|
||||||
|
if (codeBackgroundColor != 0) {
|
||||||
|
color = codeBackgroundColor;
|
||||||
|
} else {
|
||||||
|
color = ColorUtils.applyAlpha(paint.getColor(), CODE_DEF_BACKGROUND_COLOR_ALPHA);
|
||||||
|
}
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void applyHeadingTextStyle(@NonNull Paint paint, @IntRange(from = 1, to = 6) int level) {
|
||||||
|
paint.setFakeBoldText(true);
|
||||||
|
paint.setTextSize(paint.getTextSize() * HEADING_SIZES[level - 1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void applyHeadingBreakStyle(@NonNull Paint paint) {
|
||||||
|
final int color;
|
||||||
|
if (headingBreakColor != 0) {
|
||||||
|
color = headingBreakColor;
|
||||||
|
} else {
|
||||||
|
color = ColorUtils.applyAlpha(paint.getColor(), HEADING_DEF_BREAK_COLOR_ALPHA);
|
||||||
|
}
|
||||||
|
paint.setColor(color);
|
||||||
|
paint.setStyle(Paint.Style.FILL);
|
||||||
|
if (headingBreakHeight != 0) {
|
||||||
|
//noinspection SuspiciousNameCombination
|
||||||
|
paint.setStrokeWidth(headingBreakHeight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void applySuperScriptStyle(@NonNull TextPaint paint) {
|
||||||
|
final float ratio;
|
||||||
|
if (Float.compare(scriptTextSizeRatio, .0F) == 0) {
|
||||||
|
ratio = SCRIPT_DEF_TEXT_SIZE_RATIO;
|
||||||
|
} else {
|
||||||
|
ratio = scriptTextSizeRatio;
|
||||||
|
}
|
||||||
|
paint.setTextSize(paint.getTextSize() * ratio);
|
||||||
|
paint.baselineShift += (int) (paint.ascent() / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void applySubScriptStyle(@NonNull TextPaint paint) {
|
||||||
|
final float ratio;
|
||||||
|
if (Float.compare(scriptTextSizeRatio, .0F) == 0) {
|
||||||
|
ratio = SCRIPT_DEF_TEXT_SIZE_RATIO;
|
||||||
|
} else {
|
||||||
|
ratio = scriptTextSizeRatio;
|
||||||
|
}
|
||||||
|
paint.setTextSize(paint.getTextSize() * ratio);
|
||||||
|
paint.baselineShift -= (int) (paint.ascent() / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void applyThematicBreakStyle(@NonNull Paint paint) {
|
||||||
|
final int color;
|
||||||
|
if (thematicBreakColor != 0) {
|
||||||
|
color = thematicBreakColor;
|
||||||
|
} else {
|
||||||
|
color = ColorUtils.applyAlpha(paint.getColor(), THEMATIC_BREAK_DEF_ALPHA);
|
||||||
|
}
|
||||||
|
paint.setColor(color);
|
||||||
|
paint.setStyle(Paint.Style.FILL);
|
||||||
|
|
||||||
|
if (thematicBreakHeight != 0) {
|
||||||
|
//noinspection SuspiciousNameCombination
|
||||||
|
paint.setStrokeWidth(thematicBreakHeight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Builder {
|
||||||
|
|
||||||
|
private int linkColor;
|
||||||
|
private int blockMargin;
|
||||||
|
private int blockQuoteWidth;
|
||||||
|
private int blockQuoteColor;
|
||||||
|
private int listItemColor;
|
||||||
|
private int bulletListItemStrokeWidth;
|
||||||
|
private int bulletWidth;
|
||||||
|
private int codeTextColor;
|
||||||
|
private int codeBackgroundColor;
|
||||||
|
private int codeMultilineMargin;
|
||||||
|
private Typeface codeTypeface;
|
||||||
|
private int codeTextSize;
|
||||||
|
private int headingBreakHeight;
|
||||||
|
private int headingBreakColor;
|
||||||
|
private float scriptTextSizeRatio;
|
||||||
|
private int thematicBreakColor;
|
||||||
|
private int thematicBreakHeight;
|
||||||
|
|
||||||
|
Builder() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Builder(@NonNull SpannableTheme theme) {
|
||||||
|
|
||||||
|
this.linkColor = theme.linkColor;
|
||||||
|
this.blockMargin = theme.blockMargin;
|
||||||
|
this.blockQuoteWidth = theme.blockQuoteWidth;
|
||||||
|
this.blockQuoteColor = theme.blockQuoteColor;
|
||||||
|
this.listItemColor = theme.listItemColor;
|
||||||
|
this.bulletListItemStrokeWidth = theme.bulletListItemStrokeWidth;
|
||||||
|
this.bulletWidth = theme.bulletWidth;
|
||||||
|
this.codeTextColor = theme.codeTextColor;
|
||||||
|
this.codeBackgroundColor = theme.codeBackgroundColor;
|
||||||
|
this.codeMultilineMargin = theme.codeMultilineMargin;
|
||||||
|
this.codeTypeface = theme.codeTypeface;
|
||||||
|
this.codeTextSize = theme.codeTextSize;
|
||||||
|
this.headingBreakHeight = theme.headingBreakHeight;
|
||||||
|
this.headingBreakColor = theme.headingBreakColor;
|
||||||
|
this.scriptTextSizeRatio = theme.scriptTextSizeRatio;
|
||||||
|
this.thematicBreakColor = theme.thematicBreakColor;
|
||||||
|
this.thematicBreakHeight = theme.thematicBreakHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder linkColor(int linkColor) {
|
||||||
|
this.linkColor = linkColor;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder blockMargin(int blockMargin) {
|
||||||
|
this.blockMargin = blockMargin;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder blockQuoteWidth(int blockQuoteWidth) {
|
||||||
|
this.blockQuoteWidth = blockQuoteWidth;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder blockQuoteColor(int blockQuoteColor) {
|
||||||
|
this.blockQuoteColor = blockQuoteColor;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder listItemColor(int listItemColor) {
|
||||||
|
this.listItemColor = listItemColor;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder bulletListItemStrokeWidth(int bulletListItemStrokeWidth) {
|
||||||
|
this.bulletListItemStrokeWidth = bulletListItemStrokeWidth;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder bulletWidth(int bulletWidth) {
|
||||||
|
this.bulletWidth = bulletWidth;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder codeTextColor(int codeTextColor) {
|
||||||
|
this.codeTextColor = codeTextColor;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder codeBackgroundColor(int codeBackgroundColor) {
|
||||||
|
this.codeBackgroundColor = codeBackgroundColor;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder codeMultilineMargin(int codeMultilineMargin) {
|
||||||
|
this.codeMultilineMargin = codeMultilineMargin;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder codeTypeface(Typeface codeTypeface) {
|
||||||
|
this.codeTypeface = codeTypeface;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder codeTextSize(int codeTextSize) {
|
||||||
|
this.codeTextSize = codeTextSize;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder headingBreakHeight(int headingBreakHeight) {
|
||||||
|
this.headingBreakHeight = headingBreakHeight;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder headingBreakColor(int headingBreakColor) {
|
||||||
|
this.headingBreakColor = headingBreakColor;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder scriptTextSizeRatio(float scriptTextSizeRatio) {
|
||||||
|
this.scriptTextSizeRatio = scriptTextSizeRatio;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder thematicBreakColor(int thematicBreakColor) {
|
||||||
|
this.thematicBreakColor = thematicBreakColor;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder thematicBreakHeight(int thematicBreakHeight) {
|
||||||
|
this.thematicBreakHeight = thematicBreakHeight;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SpannableTheme build() {
|
||||||
|
return new SpannableTheme(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Px {
|
||||||
|
private final float density;
|
||||||
|
|
||||||
|
Px(@NonNull Context context) {
|
||||||
|
this.density = context.getResources().getDisplayMetrics().density;
|
||||||
|
}
|
||||||
|
|
||||||
|
int px(int dp) {
|
||||||
|
return (int) (dp * density + .5F);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
package ru.noties.markwon.spans;
|
||||||
|
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.text.TextPaint;
|
||||||
|
import android.text.style.MetricAffectingSpan;
|
||||||
|
|
||||||
|
public class SubScriptSpan extends MetricAffectingSpan {
|
||||||
|
|
||||||
|
private final SpannableTheme theme;
|
||||||
|
|
||||||
|
public SubScriptSpan(@NonNull SpannableTheme theme) {
|
||||||
|
this.theme = theme;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateDrawState(TextPaint tp) {
|
||||||
|
apply(tp);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateMeasureState(TextPaint tp) {
|
||||||
|
apply(tp);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void apply(TextPaint paint) {
|
||||||
|
theme.applySubScriptStyle(paint);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
package ru.noties.markwon.spans;
|
||||||
|
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.text.TextPaint;
|
||||||
|
import android.text.style.MetricAffectingSpan;
|
||||||
|
|
||||||
|
public class SuperScriptSpan extends MetricAffectingSpan {
|
||||||
|
|
||||||
|
private final SpannableTheme theme;
|
||||||
|
|
||||||
|
public SuperScriptSpan(@NonNull SpannableTheme theme) {
|
||||||
|
this.theme = theme;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateDrawState(TextPaint tp) {
|
||||||
|
apply(tp);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateMeasureState(TextPaint tp) {
|
||||||
|
apply(tp);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void apply(TextPaint paint) {
|
||||||
|
theme.applySuperScriptStyle(paint);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
package ru.noties.markwon.spans;
|
||||||
|
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.text.Layout;
|
||||||
|
import android.text.style.LeadingMarginSpan;
|
||||||
|
|
||||||
|
public class ThematicBreakSpan implements LeadingMarginSpan {
|
||||||
|
|
||||||
|
private final SpannableTheme theme;
|
||||||
|
private final Rect rect = ObjectsPool.rect();
|
||||||
|
private final Paint paint = ObjectsPool.paint();
|
||||||
|
|
||||||
|
public ThematicBreakSpan(@NonNull SpannableTheme theme) {
|
||||||
|
this.theme = theme;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getLeadingMargin(boolean first) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void drawLeadingMargin(Canvas c, Paint p, int x, int dir, int top, int baseline, int bottom, CharSequence text, int start, int end, boolean first, Layout layout) {
|
||||||
|
|
||||||
|
final int middle = top + ((bottom - top) / 2);
|
||||||
|
|
||||||
|
paint.set(p);
|
||||||
|
theme.applyThematicBreakStyle(paint);
|
||||||
|
|
||||||
|
final int height = (int) (paint.getStrokeWidth() + .5F);
|
||||||
|
final int halfHeight = (int) (height / 2.F + .5F);
|
||||||
|
|
||||||
|
rect.set(x, middle - halfHeight, c.getWidth(), middle + halfHeight);
|
||||||
|
c.drawRect(rect, paint);
|
||||||
|
}
|
||||||
|
}
|
@ -1,18 +0,0 @@
|
|||||||
apply plugin: 'com.android.library'
|
|
||||||
|
|
||||||
android {
|
|
||||||
|
|
||||||
compileSdkVersion TARGET_SDK
|
|
||||||
buildToolsVersion BUILD_TOOLS
|
|
||||||
|
|
||||||
defaultConfig {
|
|
||||||
minSdkVersion MIN_SDK
|
|
||||||
targetSdkVersion TARGET_SDK
|
|
||||||
versionCode 1
|
|
||||||
versionName version
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
compile SUPPORT_ANNOTATIONS
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
<manifest package="ru.noties.markwon.spans" />
|
|
@ -1,84 +0,0 @@
|
|||||||
package ru.noties.markwon.spans;
|
|
||||||
|
|
||||||
import android.graphics.Canvas;
|
|
||||||
import android.graphics.Paint;
|
|
||||||
import android.graphics.Rect;
|
|
||||||
import android.support.annotation.ColorInt;
|
|
||||||
import android.support.annotation.IntRange;
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
import android.text.Layout;
|
|
||||||
import android.text.style.LeadingMarginSpan;
|
|
||||||
|
|
||||||
public class BlockQuoteSpan implements LeadingMarginSpan {
|
|
||||||
|
|
||||||
private static final int DEF_COLOR_ALPHA = 50;
|
|
||||||
|
|
||||||
@SuppressWarnings("WeakerAccess")
|
|
||||||
public static class Config {
|
|
||||||
|
|
||||||
final int totalWidth;
|
|
||||||
final int quoteWidth; // by default 1/4 of width
|
|
||||||
final int quoteColor; // by default textColor with 0.2 alpha
|
|
||||||
|
|
||||||
public Config(
|
|
||||||
@IntRange(from = 1) int totalWidth,
|
|
||||||
@IntRange(from = 0) int quoteWidth,
|
|
||||||
@ColorInt int quoteColor) {
|
|
||||||
this.totalWidth = totalWidth;
|
|
||||||
this.quoteWidth = quoteWidth;
|
|
||||||
this.quoteColor = quoteColor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final Config config;
|
|
||||||
private final Rect rect = ObjectsPool.rect();
|
|
||||||
private final Paint paint = ObjectsPool.paint();
|
|
||||||
private final int indent;
|
|
||||||
|
|
||||||
public BlockQuoteSpan(@NonNull Config config, int indent) {
|
|
||||||
this.config = config;
|
|
||||||
this.indent = indent;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getLeadingMargin(boolean first) {
|
|
||||||
return config.totalWidth;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void drawLeadingMargin(
|
|
||||||
Canvas c,
|
|
||||||
Paint p,
|
|
||||||
int x,
|
|
||||||
int dir,
|
|
||||||
int top,
|
|
||||||
int baseline,
|
|
||||||
int bottom,
|
|
||||||
CharSequence text,
|
|
||||||
int start,
|
|
||||||
int end,
|
|
||||||
boolean first,
|
|
||||||
Layout layout) {
|
|
||||||
|
|
||||||
final int width;
|
|
||||||
if (config.quoteWidth == 0) {
|
|
||||||
width = (int) (config.totalWidth / 4.F + .5F);
|
|
||||||
} else {
|
|
||||||
width = config.quoteWidth;
|
|
||||||
}
|
|
||||||
|
|
||||||
final int color;
|
|
||||||
if (config.quoteColor != 0) {
|
|
||||||
color = config.quoteColor;
|
|
||||||
} else {
|
|
||||||
color = ColorUtils.applyAlpha(p.getColor(), DEF_COLOR_ALPHA);
|
|
||||||
}
|
|
||||||
paint.setStyle(Paint.Style.FILL);
|
|
||||||
paint.setColor(color);
|
|
||||||
|
|
||||||
final int left = config.totalWidth * (indent - 1);
|
|
||||||
rect.set(left, top, left + width, bottom);
|
|
||||||
|
|
||||||
c.drawRect(rect, paint);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,146 +0,0 @@
|
|||||||
package ru.noties.markwon.spans;
|
|
||||||
|
|
||||||
import android.graphics.Canvas;
|
|
||||||
import android.graphics.Paint;
|
|
||||||
import android.graphics.Rect;
|
|
||||||
import android.graphics.Typeface;
|
|
||||||
import android.support.annotation.ColorInt;
|
|
||||||
import android.support.annotation.IntRange;
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
import android.text.Layout;
|
|
||||||
import android.text.TextPaint;
|
|
||||||
import android.text.style.LeadingMarginSpan;
|
|
||||||
import android.text.style.MetricAffectingSpan;
|
|
||||||
|
|
||||||
public class CodeSpan extends MetricAffectingSpan implements LeadingMarginSpan {
|
|
||||||
|
|
||||||
private static final int DEF_COLOR_ALPHA = 25;
|
|
||||||
|
|
||||||
@SuppressWarnings("WeakerAccess")
|
|
||||||
public static class Config {
|
|
||||||
|
|
||||||
public static Builder builder() {
|
|
||||||
return new Builder();
|
|
||||||
}
|
|
||||||
|
|
||||||
final int textColor; // by default the same as main text
|
|
||||||
final int backgroundColor; // by default textColor with 0.1 alpha
|
|
||||||
final int multilineMargin; // by default 0
|
|
||||||
final int textSize; // by default the same as main text
|
|
||||||
final Typeface typeface; // by default Typeface.MONOSPACE
|
|
||||||
|
|
||||||
private Config(Builder builder) {
|
|
||||||
this.textColor = builder.textColor;
|
|
||||||
this.backgroundColor = builder.backgroundColor;
|
|
||||||
this.multilineMargin = builder.multilineMargin;
|
|
||||||
this.textSize = builder.textSize;
|
|
||||||
this.typeface = builder.typeface;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Builder {
|
|
||||||
|
|
||||||
int textColor;
|
|
||||||
int backgroundColor;
|
|
||||||
int multilineMargin;
|
|
||||||
int textSize;
|
|
||||||
Typeface typeface;
|
|
||||||
|
|
||||||
public Builder setTextColor(@ColorInt int textColor) {
|
|
||||||
this.textColor = textColor;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder setBackgroundColor(@ColorInt int backgroundColor) {
|
|
||||||
this.backgroundColor = backgroundColor;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder setMultilineMargin(int multilineMargin) {
|
|
||||||
this.multilineMargin = multilineMargin;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder setTextSize(@IntRange(from = 0) int textSize) {
|
|
||||||
this.textSize = textSize;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder setTypeface(@NonNull Typeface typeface) {
|
|
||||||
this.typeface = typeface;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Config build() {
|
|
||||||
if (typeface == null) {
|
|
||||||
typeface = Typeface.MONOSPACE;
|
|
||||||
}
|
|
||||||
return new Config(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final Config config;
|
|
||||||
private final Rect rect = ObjectsPool.rect();
|
|
||||||
private final Paint paint = ObjectsPool.paint();
|
|
||||||
|
|
||||||
private final boolean multiline;
|
|
||||||
|
|
||||||
public CodeSpan(@NonNull Config config, boolean multiline) {
|
|
||||||
this.config = config;
|
|
||||||
this.multiline = multiline;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void updateMeasureState(TextPaint p) {
|
|
||||||
apply(p);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void updateDrawState(TextPaint ds) {
|
|
||||||
apply(ds);
|
|
||||||
if (!multiline) {
|
|
||||||
final int color;
|
|
||||||
if (config.backgroundColor == 0) {
|
|
||||||
color = ColorUtils.applyAlpha(ds.getColor(), DEF_COLOR_ALPHA);
|
|
||||||
} else {
|
|
||||||
color = config.backgroundColor;
|
|
||||||
}
|
|
||||||
ds.bgColor = color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void apply(TextPaint p) {
|
|
||||||
p.setTypeface(config.typeface);
|
|
||||||
if (config.textSize > 0) {
|
|
||||||
p.setTextSize(config.textSize);
|
|
||||||
}
|
|
||||||
if (config.textColor != 0) {
|
|
||||||
p.setColor(config.textColor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getLeadingMargin(boolean first) {
|
|
||||||
return multiline ? config.multilineMargin : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void drawLeadingMargin(Canvas c, Paint p, int x, int dir, int top, int baseline, int bottom, CharSequence text, int start, int end, boolean first, Layout layout) {
|
|
||||||
|
|
||||||
if (multiline) {
|
|
||||||
|
|
||||||
final int color;
|
|
||||||
if (config.backgroundColor == 0) {
|
|
||||||
color = ColorUtils.applyAlpha(p.getColor(), DEF_COLOR_ALPHA);
|
|
||||||
} else {
|
|
||||||
color = config.backgroundColor;
|
|
||||||
}
|
|
||||||
paint.setStyle(Paint.Style.FILL);
|
|
||||||
paint.setColor(color);
|
|
||||||
|
|
||||||
rect.set(x, top, c.getWidth(), bottom);
|
|
||||||
|
|
||||||
c.drawRect(rect, paint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,97 +0,0 @@
|
|||||||
package ru.noties.markwon.spans;
|
|
||||||
|
|
||||||
import android.graphics.Canvas;
|
|
||||||
import android.graphics.Paint;
|
|
||||||
import android.graphics.Rect;
|
|
||||||
import android.support.annotation.ColorInt;
|
|
||||||
import android.support.annotation.IntRange;
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
import android.text.Layout;
|
|
||||||
import android.text.TextPaint;
|
|
||||||
import android.text.style.LeadingMarginSpan;
|
|
||||||
import android.text.style.MetricAffectingSpan;
|
|
||||||
|
|
||||||
public class HeadingSpan extends MetricAffectingSpan implements LeadingMarginSpan {
|
|
||||||
|
|
||||||
// taken from html spec (most browsers render headings like that)
|
|
||||||
private static final float[] HEADING_SIZES = {
|
|
||||||
2.F, 1.5F, 1.17F, 1.F, .83F, .67F,
|
|
||||||
};
|
|
||||||
|
|
||||||
private static final int DEF_BREAK_COLOR_ALPHA = 127;
|
|
||||||
|
|
||||||
public static class Config {
|
|
||||||
|
|
||||||
final int breakHeight; // by default stroke width
|
|
||||||
final int breakColor; // by default -> textColor with 0.5 alpha
|
|
||||||
|
|
||||||
public Config(@IntRange(from = 0) int breakHeight, @ColorInt int breakColor) {
|
|
||||||
this.breakHeight = breakHeight;
|
|
||||||
this.breakColor = breakColor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final Config config;
|
|
||||||
private final Rect rect = ObjectsPool.rect();
|
|
||||||
private final Paint paint = ObjectsPool.paint();
|
|
||||||
private final int level;
|
|
||||||
private final int end;
|
|
||||||
|
|
||||||
public HeadingSpan(@NonNull Config config, @IntRange(from = 1, to = 6) int level, @IntRange(from = 0) int end) {
|
|
||||||
this.config = config;
|
|
||||||
this.level = level - 1;
|
|
||||||
this.end = end;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void updateMeasureState(TextPaint p) {
|
|
||||||
apply(p);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void updateDrawState(TextPaint tp) {
|
|
||||||
apply(tp);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void apply(TextPaint paint) {
|
|
||||||
paint.setTextSize(paint.getTextSize() * HEADING_SIZES[level]);
|
|
||||||
paint.setFakeBoldText(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getLeadingMargin(boolean first) {
|
|
||||||
// no margin actually, but we need to access Canvas to draw break
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void drawLeadingMargin(Canvas c, Paint p, int x, int dir, int top, int baseline, int bottom, CharSequence text, int start, int end, boolean first, Layout layout) {
|
|
||||||
|
|
||||||
// if we are configured to draw underlines, draw them here
|
|
||||||
|
|
||||||
if (level == 0
|
|
||||||
|| level == 1) {
|
|
||||||
|
|
||||||
if (this.end == end) {
|
|
||||||
|
|
||||||
final int color;
|
|
||||||
final int breakHeight;
|
|
||||||
if (config.breakColor == 0) {
|
|
||||||
color = ColorUtils.applyAlpha(p.getColor(), DEF_BREAK_COLOR_ALPHA);
|
|
||||||
} else {
|
|
||||||
color = config.breakColor;
|
|
||||||
}
|
|
||||||
if (config.breakHeight == 0) {
|
|
||||||
breakHeight = (int) (p.getStrokeWidth() + .5F);
|
|
||||||
} else {
|
|
||||||
breakHeight = config.breakHeight;
|
|
||||||
}
|
|
||||||
paint.setStyle(Paint.Style.FILL);
|
|
||||||
paint.setColor(color);
|
|
||||||
|
|
||||||
rect.set(x, bottom - breakHeight, c.getWidth(), bottom);
|
|
||||||
c.drawRect(rect, paint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
package ru.noties.markwon.spans;
|
|
||||||
|
|
||||||
import android.text.TextPaint;
|
|
||||||
import android.text.style.MetricAffectingSpan;
|
|
||||||
|
|
||||||
public class SubSpan extends MetricAffectingSpan {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void updateDrawState(TextPaint tp) {
|
|
||||||
tp.setTextSize(tp.getTextSize() * .75F);
|
|
||||||
tp.baselineShift -= (int) (tp.ascent() / 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void updateMeasureState(TextPaint tp) {
|
|
||||||
tp.setTextSize(tp.getTextSize() * .75F);
|
|
||||||
tp.baselineShift -= (int) (tp.ascent() / 2);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
package ru.noties.markwon.spans;
|
|
||||||
|
|
||||||
import android.text.TextPaint;
|
|
||||||
import android.text.style.MetricAffectingSpan;
|
|
||||||
|
|
||||||
public class SupSpan extends MetricAffectingSpan {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void updateDrawState(TextPaint tp) {
|
|
||||||
tp.setTextSize(tp.getTextSize() * .75F);
|
|
||||||
tp.baselineShift += (int) (tp.ascent() / 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void updateMeasureState(TextPaint tp) {
|
|
||||||
tp.setTextSize(tp.getTextSize() * .75F);
|
|
||||||
tp.baselineShift += (int) (tp.ascent() / 2);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,64 +0,0 @@
|
|||||||
package ru.noties.markwon.spans;
|
|
||||||
|
|
||||||
import android.graphics.Canvas;
|
|
||||||
import android.graphics.Paint;
|
|
||||||
import android.graphics.Rect;
|
|
||||||
import android.support.annotation.ColorInt;
|
|
||||||
import android.support.annotation.IntRange;
|
|
||||||
import android.text.Layout;
|
|
||||||
import android.text.style.LeadingMarginSpan;
|
|
||||||
|
|
||||||
public class ThematicBreakSpan implements LeadingMarginSpan {
|
|
||||||
|
|
||||||
private static final int DEF_COLOR_ALPHA = 127;
|
|
||||||
|
|
||||||
public static class Config {
|
|
||||||
|
|
||||||
final int color; // by default textColor with 0.5 alpha
|
|
||||||
final int height; // by default strokeWidth of paint
|
|
||||||
|
|
||||||
public Config(@ColorInt int color, @IntRange(from = 0) int height) {
|
|
||||||
this.color = color;
|
|
||||||
this.height = height;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final Config config;
|
|
||||||
private final Rect rect = ObjectsPool.rect();
|
|
||||||
private final Paint paint = ObjectsPool.paint();
|
|
||||||
|
|
||||||
public ThematicBreakSpan(Config config) {
|
|
||||||
this.config = config;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getLeadingMargin(boolean first) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void drawLeadingMargin(Canvas c, Paint p, int x, int dir, int top, int baseline, int bottom, CharSequence text, int start, int end, boolean first, Layout layout) {
|
|
||||||
|
|
||||||
final int middle = top + ((bottom - top) / 2);
|
|
||||||
|
|
||||||
final int color;
|
|
||||||
if (config.color == 0) {
|
|
||||||
color = ColorUtils.applyAlpha(p.getColor(), DEF_COLOR_ALPHA);
|
|
||||||
} else {
|
|
||||||
color = config.color;
|
|
||||||
}
|
|
||||||
paint.setColor(color);
|
|
||||||
paint.setStyle(Paint.Style.FILL);
|
|
||||||
|
|
||||||
final int height;
|
|
||||||
if (config.height == 0) {
|
|
||||||
height = (int) (p.getStrokeWidth() + .5F);
|
|
||||||
} else {
|
|
||||||
height = config.height;
|
|
||||||
}
|
|
||||||
final int halfHeight = (int) (height / 2.F + .5F);
|
|
||||||
|
|
||||||
rect.set(x, middle - halfHeight, c.getWidth(), middle + halfHeight);
|
|
||||||
c.drawRect(rect, paint);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1 +1 @@
|
|||||||
include ':app', ':library-spans', ':library-renderer', ':library-view'
|
include ':app', ':library-renderer', ':library-view'
|
||||||
|
Loading…
x
Reference in New Issue
Block a user