Introduced SpannableTheme
This commit is contained in:
parent
e50789bc40
commit
e0c10c658b
@ -18,4 +18,5 @@ dependencies {
|
||||
compile project(':library-renderer')
|
||||
compile 'ru.noties:debug:3.0.0@jar'
|
||||
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.spans.AsyncDrawable;
|
||||
import ru.noties.markwon.spans.CodeSpan;
|
||||
import ru.noties.markwon.spans.DrawableSpanUtils;
|
||||
import ru.noties.markwon.spans.AsyncDrawableSpanUtils;
|
||||
|
||||
public class MainActivity extends Activity {
|
||||
|
||||
@ -101,50 +101,52 @@ public class MainActivity extends Activity {
|
||||
.build();
|
||||
final Node node = parser.parse(md);
|
||||
|
||||
final SpannableConfiguration configuration = SpannableConfiguration.builder(MainActivity.this)
|
||||
.setAsyncDrawableLoader(new AsyncDrawable.Loader() {
|
||||
@Override
|
||||
public void load(@NonNull String destination, @NonNull final AsyncDrawable drawable) {
|
||||
Debug.i(destination);
|
||||
final Target target = new Target() {
|
||||
@Override
|
||||
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
|
||||
Debug.i();
|
||||
final Drawable d = new BitmapDrawable(getResources(), bitmap);
|
||||
d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
|
||||
drawable.setResult(d);
|
||||
// textView.setText(textView.getText());
|
||||
}
|
||||
// final SpannableConfiguration configuration = SpannableConfiguration.builder(MainActivity.this)
|
||||
// .setAsyncDrawableLoader(new AsyncDrawable.Loader() {
|
||||
// @Override
|
||||
// public void load(@NonNull String destination, @NonNull final AsyncDrawable drawable) {
|
||||
// Debug.i(destination);
|
||||
// final Target target = new Target() {
|
||||
// @Override
|
||||
// public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
|
||||
// Debug.i();
|
||||
// final Drawable d = new BitmapDrawable(getResources(), bitmap);
|
||||
// d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
|
||||
// drawable.setResult(d);
|
||||
//// 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
|
||||
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 SpannableConfiguration configuration = SpannableConfiguration.create(MainActivity.this);
|
||||
|
||||
final CharSequence text = new ru.noties.markwon.renderer.SpannableRenderer().render(
|
||||
configuration,
|
||||
@ -168,7 +170,7 @@ public class MainActivity extends Activity {
|
||||
// NB! LinkMovementMethod forces frequent updates...
|
||||
textView.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
textView.setText(text);
|
||||
DrawableSpanUtils.scheduleDrawables(textView);
|
||||
AsyncDrawableSpanUtils.scheduleDrawables(textView);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -53,8 +53,8 @@
|
||||
//import ru.noties.markwon.spans.EmphasisSpan;
|
||||
//import ru.noties.markwon.spans.BulletListItemSpan;
|
||||
//import ru.noties.markwon.spans.StrongEmphasisSpan;
|
||||
//import ru.noties.markwon.spans.SubSpan;
|
||||
//import ru.noties.markwon.spans.SupSpan;
|
||||
//import ru.noties.markwon.spans.SubScriptSpan;
|
||||
//import ru.noties.markwon.spans.SuperScriptSpan;
|
||||
//import ru.noties.markwon.spans.ThematicBreakSpan;
|
||||
//
|
||||
//public class SpannableRenderer implements Renderer {
|
||||
@ -263,9 +263,9 @@
|
||||
// final int end = builder.length();
|
||||
// // here, additionally, we can render some tags ourselves (sup/sub)
|
||||
// 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)) {
|
||||
// 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)) {
|
||||
// // weird, but `Html` class does not return a spannable for `<del>o</del>`
|
||||
// // seems like a bug
|
||||
|
@ -5,7 +5,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<TextView
|
||||
android:id="@+id/activity_main"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="colorPrimary">#3F51B5</color>
|
||||
<color name="colorPrimaryDark">#303F9F</color>
|
||||
<color name="colorAccent">#FF4081</color>
|
||||
<color name="colorPrimary">#424242</color>
|
||||
<color name="colorPrimaryDark">#212121</color>
|
||||
<color name="colorAccent">#4caf50</color>
|
||||
</resources>
|
||||
|
@ -3,7 +3,7 @@ buildscript {
|
||||
jcenter()
|
||||
}
|
||||
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 {
|
||||
|
||||
compile project(':library-spans')
|
||||
|
||||
compile SUPPORT_ANNOTATIONS
|
||||
compile COMMON_MARK
|
||||
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 ru.noties.markwon.spans.AsyncDrawable;
|
||||
import ru.noties.markwon.spans.BlockQuoteSpan;
|
||||
import ru.noties.markwon.spans.BulletListItemSpan;
|
||||
import ru.noties.markwon.spans.CodeSpan;
|
||||
import ru.noties.markwon.spans.HeadingSpan;
|
||||
import ru.noties.markwon.spans.OrderedListItemSpan;
|
||||
import ru.noties.markwon.spans.ThematicBreakSpan;
|
||||
import ru.noties.markwon.spans.LinkSpan;
|
||||
import ru.noties.markwon.spans.SpannableTheme;
|
||||
|
||||
public class SpannableConfiguration {
|
||||
|
||||
@ -22,140 +18,81 @@ public class SpannableConfiguration {
|
||||
return new Builder(context);
|
||||
}
|
||||
|
||||
private final BlockQuoteSpan.Config blockQuoteConfig;
|
||||
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 SpannableTheme theme;
|
||||
private final AsyncDrawable.Loader asyncDrawableLoader;
|
||||
private final SyntaxHighlight syntaxHighlight;
|
||||
private final LinkSpan.Resolver linkResolver;
|
||||
|
||||
private SpannableConfiguration(Builder builder) {
|
||||
this.blockQuoteConfig = builder.blockQuoteConfig;
|
||||
this.codeConfig = builder.codeConfig;
|
||||
this.bulletListConfig = builder.bulletListConfig;
|
||||
this.headingConfig = builder.headingConfig;
|
||||
this.thematicConfig = builder.thematicConfig;
|
||||
this.orderedListConfig = builder.orderedListConfig;
|
||||
this.theme = builder.theme;
|
||||
this.asyncDrawableLoader = builder.asyncDrawableLoader;
|
||||
this.syntaxHighlight = builder.syntaxHighlight;
|
||||
this.linkResolver = builder.linkResolver;
|
||||
}
|
||||
|
||||
public BlockQuoteSpan.Config getBlockQuoteConfig() {
|
||||
return blockQuoteConfig;
|
||||
public SpannableTheme theme() {
|
||||
return theme;
|
||||
}
|
||||
|
||||
public CodeSpan.Config getCodeConfig() {
|
||||
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() {
|
||||
public AsyncDrawable.Loader asyncDrawableLoader() {
|
||||
return asyncDrawableLoader;
|
||||
}
|
||||
|
||||
public SyntaxHighlight syntaxHighlight() {
|
||||
return syntaxHighlight;
|
||||
}
|
||||
|
||||
public LinkSpan.Resolver linkResolver() {
|
||||
return linkResolver;
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private final Context context;
|
||||
private BlockQuoteSpan.Config blockQuoteConfig;
|
||||
private CodeSpan.Config codeConfig;
|
||||
private BulletListItemSpan.Config bulletListConfig;
|
||||
private HeadingSpan.Config headingConfig;
|
||||
private ThematicBreakSpan.Config thematicConfig;
|
||||
private OrderedListItemSpan.Config orderedListConfig;
|
||||
private SpannableTheme theme;
|
||||
private AsyncDrawable.Loader asyncDrawableLoader;
|
||||
private SyntaxHighlight syntaxHighlight;
|
||||
private LinkSpan.Resolver linkResolver;
|
||||
|
||||
public Builder(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public Builder setBlockQuoteConfig(@NonNull BlockQuoteSpan.Config blockQuoteConfig) {
|
||||
this.blockQuoteConfig = blockQuoteConfig;
|
||||
public Builder theme(SpannableTheme theme) {
|
||||
this.theme = theme;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setCodeConfig(@NonNull CodeSpan.Config codeConfig) {
|
||||
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) {
|
||||
public Builder asyncDrawableLoader(AsyncDrawable.Loader asyncDrawableLoader) {
|
||||
this.asyncDrawableLoader = asyncDrawableLoader;
|
||||
return this;
|
||||
}
|
||||
|
||||
// todo, change to something more reliable
|
||||
// todo, must mention that bullet/ordered/quote must have the same margin (or maybe we can just enforce it?)
|
||||
// actually it does make sense to have `blockQuote` & `list` margins (if they are different)
|
||||
// todo, maybe move defaults to configuration classes?
|
||||
public Builder syntaxHighlight(SyntaxHighlight syntaxHighlight) {
|
||||
this.syntaxHighlight = syntaxHighlight;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder linkResolver(LinkSpan.Resolver linkResolver) {
|
||||
this.linkResolver = linkResolver;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SpannableConfiguration build() {
|
||||
if (blockQuoteConfig == null) {
|
||||
blockQuoteConfig = new BlockQuoteSpan.Config(
|
||||
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 (theme == null) {
|
||||
theme = SpannableTheme.create(context);
|
||||
}
|
||||
if (asyncDrawableLoader == null) {
|
||||
asyncDrawableLoader = new AsyncDrawableLoaderNoOp();
|
||||
}
|
||||
if (syntaxHighlight == null) {
|
||||
syntaxHighlight = new SyntaxHighlightNoOp();
|
||||
}
|
||||
if (linkResolver == null) {
|
||||
linkResolver = new LinkResolverDef();
|
||||
}
|
||||
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.text.SpannableStringBuilder;
|
||||
import android.text.Spanned;
|
||||
import android.text.TextUtils;
|
||||
import android.text.style.StrikethroughSpan;
|
||||
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.EmphasisSpan;
|
||||
import ru.noties.markwon.spans.HeadingSpan;
|
||||
import ru.noties.markwon.spans.LinkSpan;
|
||||
import ru.noties.markwon.spans.OrderedListItemSpan;
|
||||
import ru.noties.markwon.spans.StrongEmphasisSpan;
|
||||
import ru.noties.markwon.spans.ThematicBreakSpan;
|
||||
@ -97,7 +97,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
|
||||
visitChildren(blockQuote);
|
||||
|
||||
setSpan(length, new BlockQuoteSpan(
|
||||
configuration.getBlockQuoteConfig(),
|
||||
configuration.theme(),
|
||||
blockQuoteIndent
|
||||
));
|
||||
|
||||
@ -123,7 +123,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
|
||||
builder.append('\u00a0');
|
||||
|
||||
setSpan(length, new CodeSpan(
|
||||
configuration.getCodeConfig(),
|
||||
configuration.theme(),
|
||||
false
|
||||
));
|
||||
}
|
||||
@ -131,17 +131,20 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
|
||||
@Override
|
||||
public void visit(FencedCodeBlock fencedCodeBlock) {
|
||||
|
||||
// Debug.i(fencedCodeBlock);
|
||||
|
||||
newLine();
|
||||
|
||||
final int length = builder.length();
|
||||
|
||||
// empty lines on top & bottom
|
||||
builder.append('\u00a0').append('\n');
|
||||
builder.append(fencedCodeBlock.getLiteral());
|
||||
builder.append(
|
||||
configuration.syntaxHighlight()
|
||||
.highlight(fencedCodeBlock.getInfo(), fencedCodeBlock.getLiteral())
|
||||
);
|
||||
builder.append('\u00a0').append('\n');
|
||||
|
||||
setSpan(length, new CodeSpan(
|
||||
configuration.getCodeConfig(),
|
||||
configuration.theme(),
|
||||
true
|
||||
));
|
||||
|
||||
@ -189,7 +192,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
|
||||
visitChildren(listItem);
|
||||
|
||||
setSpan(length, new OrderedListItemSpan(
|
||||
configuration.getOrderedListConfig(),
|
||||
configuration.theme(),
|
||||
String.valueOf(start) + "." + '\u00a0',
|
||||
blockQuoteIndent,
|
||||
length
|
||||
@ -204,7 +207,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
|
||||
visitChildren(listItem);
|
||||
|
||||
setSpan(length, new BulletListItemSpan(
|
||||
configuration.getBulletListConfig(),
|
||||
configuration.theme(),
|
||||
blockQuoteIndent,
|
||||
listLevel - 1,
|
||||
length
|
||||
@ -226,7 +229,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
|
||||
|
||||
final int length = builder.length();
|
||||
builder.append(' '); // without space it won't render
|
||||
setSpan(length, new ThematicBreakSpan(configuration.getThematicConfig()));
|
||||
setSpan(length, new ThematicBreakSpan(configuration.theme()));
|
||||
|
||||
newLine();
|
||||
builder.append('\n');
|
||||
@ -242,7 +245,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
|
||||
final int length = builder.length();
|
||||
visitChildren(heading);
|
||||
setSpan(length, new HeadingSpan(
|
||||
configuration.getHeadingConfig(),
|
||||
configuration.theme(),
|
||||
heading.getLevel(),
|
||||
builder.length())
|
||||
);
|
||||
@ -315,7 +318,18 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
|
||||
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
|
||||
@ -329,7 +343,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
|
||||
public void visit(Link link) {
|
||||
final int length = builder.length();
|
||||
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) {
|
||||
|
@ -14,6 +14,7 @@ public class SpannableRenderer {
|
||||
// * Common interface for images (in markdown & inline-html)
|
||||
// * util method to properly copy markdown (with lists/links, etc)
|
||||
// * util to apply empty line height
|
||||
// * transform relative urls to absolute ones...
|
||||
|
||||
public CharSequence render(@NonNull SpannableConfiguration configuration, @Nullable Node node) {
|
||||
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.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.support.annotation.IntDef;
|
||||
import android.support.annotation.IntRange;
|
||||
import android.support.annotation.NonNull;
|
||||
@ -13,23 +12,39 @@ import android.text.style.ReplacementSpan;
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public class AsyncDrawableSpan extends ReplacementSpan {
|
||||
|
||||
@IntDef({ ALIGN_BOTTOM, ALIGN_BASELINE, ALIGN_CENTER })
|
||||
@interface Alignment {}
|
||||
@IntDef({ALIGN_BOTTOM, ALIGN_BASELINE, ALIGN_CENTER})
|
||||
@interface Alignment {
|
||||
}
|
||||
|
||||
public static final int ALIGN_BOTTOM = 0;
|
||||
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
|
||||
|
||||
private final SpannableTheme theme;
|
||||
private final AsyncDrawable drawable;
|
||||
private final int alignment;
|
||||
private final boolean replacementTextIsLink;
|
||||
|
||||
public AsyncDrawableSpan(@NonNull AsyncDrawable drawable) {
|
||||
this(drawable, ALIGN_BOTTOM);
|
||||
public AsyncDrawableSpan(@NonNull SpannableTheme theme, @NonNull AsyncDrawable drawable) {
|
||||
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.alignment = alignment;
|
||||
this.replacementTextIsLink = replacementTextIsLink;
|
||||
|
||||
// additionally set intrinsic bounds if empty
|
||||
final Rect rect = drawable.getBounds();
|
||||
@ -66,8 +81,13 @@ public class AsyncDrawableSpan extends ReplacementSpan {
|
||||
|
||||
} 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;
|
||||
@ -108,7 +128,15 @@ public class AsyncDrawableSpan extends ReplacementSpan {
|
||||
}
|
||||
} 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);
|
||||
}
|
||||
}
|
@ -11,7 +11,7 @@ import android.widget.TextView;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class DrawableSpanUtils {
|
||||
public class AsyncDrawableSpanUtils {
|
||||
|
||||
// todo, add `unschedule` method (to be used when new text is set, so
|
||||
// drawables are removed from callbacks)
|
||||
@ -66,7 +66,7 @@ public class DrawableSpanUtils {
|
||||
}
|
||||
}
|
||||
|
||||
private DrawableSpanUtils() {}
|
||||
private AsyncDrawableSpanUtils() {}
|
||||
|
||||
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.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.support.annotation.ColorInt;
|
||||
import android.support.annotation.IntRange;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.text.Layout;
|
||||
@ -12,30 +11,7 @@ import android.text.style.LeadingMarginSpan;
|
||||
|
||||
public class BulletListItemSpan implements LeadingMarginSpan {
|
||||
|
||||
// todo, there are 3 types of bullets: filled circle, stroke circle & filled rectangle
|
||||
// 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 SpannableTheme theme;
|
||||
|
||||
private final Paint paint = ObjectsPool.paint();
|
||||
private final RectF circle = ObjectsPool.rectF();
|
||||
@ -46,11 +22,11 @@ public class BulletListItemSpan implements LeadingMarginSpan {
|
||||
private final int start;
|
||||
|
||||
public BulletListItemSpan(
|
||||
@NonNull Config config,
|
||||
@NonNull SpannableTheme theme,
|
||||
@IntRange(from = 0) int blockIndent,
|
||||
@IntRange(from = 0) int level,
|
||||
@IntRange(from = 0) int start) {
|
||||
this.config = config;
|
||||
this.theme = theme;
|
||||
this.blockIndent = blockIndent;
|
||||
this.level = level;
|
||||
this.start = start;
|
||||
@ -58,7 +34,7 @@ public class BulletListItemSpan implements LeadingMarginSpan {
|
||||
|
||||
@Override
|
||||
public int getLeadingMargin(boolean first) {
|
||||
return config.marginWidth;
|
||||
return theme.getBlockMargin();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -69,39 +45,17 @@ public class BulletListItemSpan implements LeadingMarginSpan {
|
||||
return;
|
||||
}
|
||||
|
||||
final int color;
|
||||
final float stroke;
|
||||
paint.set(p);
|
||||
|
||||
if (config.bulletColor == 0) {
|
||||
color = p.getColor();
|
||||
} else {
|
||||
color = config.bulletColor;
|
||||
}
|
||||
|
||||
if (config.bulletStrokeWidth == 0) {
|
||||
stroke = p.getStrokeWidth();
|
||||
} else {
|
||||
stroke = config.bulletStrokeWidth;
|
||||
}
|
||||
|
||||
paint.setColor(color);
|
||||
paint.setStrokeWidth(stroke);
|
||||
theme.applyListItemStyle(paint);
|
||||
|
||||
final int save = c.save();
|
||||
try {
|
||||
|
||||
// by default we use half of margin width, but if height is less than width, we calculate from it
|
||||
final int width = config.marginWidth;
|
||||
final int width = theme.getBlockMargin();
|
||||
final int height = bottom - top;
|
||||
|
||||
final int min = Math.min(config.marginWidth, height) / 2;
|
||||
final int side;
|
||||
if (config.bulletSide == 0
|
||||
|| config.bulletSide > min) {
|
||||
side = min;
|
||||
} else {
|
||||
side = config.bulletSide;
|
||||
}
|
||||
final int side = theme.getBulletWidth(bottom - top);
|
||||
|
||||
final int marginLeft = (width - 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;
|
||||
|
||||
class ColorUtils {
|
||||
abstract class ColorUtils {
|
||||
|
||||
static int applyAlpha(int color, int alpha) {
|
||||
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.Paint;
|
||||
import android.support.annotation.ColorInt;
|
||||
import android.support.annotation.IntRange;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.text.Layout;
|
||||
@ -10,29 +9,18 @@ import android.text.style.LeadingMarginSpan;
|
||||
|
||||
public class OrderedListItemSpan implements LeadingMarginSpan {
|
||||
|
||||
public static class Config {
|
||||
|
||||
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 SpannableTheme theme;
|
||||
private final String number;
|
||||
private final int blockIndent;
|
||||
private final int start;
|
||||
|
||||
public OrderedListItemSpan(
|
||||
@NonNull Config config,
|
||||
@NonNull SpannableTheme theme,
|
||||
@NonNull String number,
|
||||
@IntRange(from = 0) int blockIndent,
|
||||
@IntRange(from = 0) int start
|
||||
) {
|
||||
this.config = config;
|
||||
this.theme = theme;
|
||||
this.number = number;
|
||||
this.blockIndent = blockIndent;
|
||||
this.start = start;
|
||||
@ -40,7 +28,7 @@ public class OrderedListItemSpan implements LeadingMarginSpan {
|
||||
|
||||
@Override
|
||||
public int getLeadingMargin(boolean first) {
|
||||
return config.marginWidth;
|
||||
return theme.getBlockMargin();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -51,15 +39,13 @@ public class OrderedListItemSpan implements LeadingMarginSpan {
|
||||
return;
|
||||
}
|
||||
|
||||
if (config.numberColor != 0) {
|
||||
p.setColor(config.numberColor);
|
||||
}
|
||||
theme.applyListItemStyle(p);
|
||||
|
||||
final int width = config.marginWidth;
|
||||
final int width = theme.getBlockMargin();
|
||||
final int numberWidth = (int) (p.measureText(number) + .5F);
|
||||
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);
|
||||
}
|
@ -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