Merge 35a41818a58405143633fde01de413081ebeb694 into 18938a1aa2e178185de82a4f605d03a0ccb3084d
This commit is contained in:
commit
d55173ad76
@ -34,6 +34,6 @@ dependencies {
|
||||
|
||||
compile OK_HTTP
|
||||
|
||||
compile 'com.google.dagger:dagger:2.10'
|
||||
annotationProcessor 'com.google.dagger:dagger-compiler:2.10'
|
||||
compile 'com.google.dagger:dagger:2.12'
|
||||
annotationProcessor 'com.google.dagger:dagger-compiler:2.12'
|
||||
}
|
||||
|
10
build.gradle
10
build.gradle
@ -4,7 +4,7 @@ buildscript {
|
||||
google()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:2.3.3'
|
||||
classpath 'com.android.tools.build:gradle:3.0.0'
|
||||
}
|
||||
}
|
||||
|
||||
@ -28,12 +28,12 @@ task wrapper(type: Wrapper) {
|
||||
ext {
|
||||
|
||||
// Config
|
||||
BUILD_TOOLS = '26.0.2'
|
||||
TARGET_SDK = 26
|
||||
BUILD_TOOLS = '27.0.0'
|
||||
TARGET_SDK = 27
|
||||
MIN_SDK = 16
|
||||
|
||||
// Dependencies
|
||||
final def supportVersion = '26.1.0'
|
||||
final def supportVersion = '27.0.0'
|
||||
SUPPORT_ANNOTATIONS = "com.android.support:support-annotations:$supportVersion"
|
||||
SUPPORT_APP_COMPAT = "com.android.support:appcompat-v7:$supportVersion"
|
||||
|
||||
@ -44,5 +44,5 @@ ext {
|
||||
|
||||
ANDROID_SVG = 'com.caverock:androidsvg:1.2.1'
|
||||
ANDROID_GIF = 'pl.droidsonroids.gif:android-gif-drawable:1.2.7'
|
||||
OK_HTTP = 'com.squareup.okhttp3:okhttp:3.8.0'
|
||||
OK_HTTP = 'com.squareup.okhttp3:okhttp:3.9.0'
|
||||
}
|
||||
|
@ -28,6 +28,8 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import okhttp3.Call;
|
||||
import okhttp3.OkHttpClient;
|
||||
@ -53,39 +55,38 @@ public class AsyncDrawableLoader implements AsyncDrawable.Loader {
|
||||
|
||||
private static final String FILE_ANDROID_ASSETS = "android_asset";
|
||||
|
||||
private final OkHttpClient client;
|
||||
private final Resources resources;
|
||||
private final ExecutorService executorService;
|
||||
private final Handler mainThread;
|
||||
private final Drawable errorDrawable;
|
||||
private final OkHttpClient mClient;
|
||||
private final Resources mResources;
|
||||
private final ExecutorService mExecutorService;
|
||||
private final Handler mMainThread;
|
||||
private final Drawable mErrorDrawable;
|
||||
|
||||
private final Map<String, Future<?>> requests;
|
||||
private final Map<String, Future<?>> mRequests;
|
||||
|
||||
AsyncDrawableLoader(Builder builder) {
|
||||
this.client = builder.client;
|
||||
this.resources = builder.resources;
|
||||
this.executorService = builder.executorService;
|
||||
this.mainThread = new Handler(Looper.getMainLooper());
|
||||
this.errorDrawable = builder.errorDrawable;
|
||||
this.requests = new HashMap<>(3);
|
||||
mClient = builder.client;
|
||||
mResources = builder.resources;
|
||||
mExecutorService = builder.executorService;
|
||||
mMainThread = new Handler(Looper.getMainLooper());
|
||||
mErrorDrawable = builder.errorDrawable;
|
||||
mRequests = new HashMap<>(3);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void load(@NonNull String destination, @NonNull AsyncDrawable drawable) {
|
||||
// if drawable is not a link -> show loading placeholder...
|
||||
requests.put(destination, execute(destination, drawable));
|
||||
mRequests.put(destination, execute(destination, drawable));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel(@NonNull String destination) {
|
||||
|
||||
final Future<?> request = requests.remove(destination);
|
||||
final Future<?> request = mRequests.remove(destination);
|
||||
if (request != null) {
|
||||
request.cancel(true);
|
||||
}
|
||||
|
||||
final List<Call> calls = client.dispatcher().queuedCalls();
|
||||
final List<Call> calls = mClient.dispatcher().queuedCalls();
|
||||
if (calls != null) {
|
||||
for (Call call : calls) {
|
||||
if (!call.isCanceled()) {
|
||||
@ -97,14 +98,27 @@ public class AsyncDrawableLoader implements AsyncDrawable.Loader {
|
||||
}
|
||||
}
|
||||
|
||||
private Future<?> execute(@NonNull final String destination, @NonNull AsyncDrawable drawable) {
|
||||
final WeakReference<AsyncDrawable> reference = new WeakReference<AsyncDrawable>(drawable);
|
||||
@NonNull
|
||||
private Future<?> execute(@NonNull final String destinationCandidate, @NonNull AsyncDrawable drawable) {
|
||||
final WeakReference<AsyncDrawable> reference = new WeakReference<>(drawable);
|
||||
// todo, if not a link -> show placeholder
|
||||
return executorService.submit(new Runnable() {
|
||||
return mExecutorService.submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
final Item item;
|
||||
int height = -1;
|
||||
int width = -1;
|
||||
Matcher matcher = Pattern.compile("(.*)/(\\d+)\\$(\\d+)").matcher(destinationCandidate);
|
||||
|
||||
final String destination;
|
||||
if (matcher.matches()){
|
||||
width = Integer.parseInt(matcher.group(2));
|
||||
height = Integer.parseInt(matcher.group(3));
|
||||
destination = matcher.group(1);
|
||||
} else {
|
||||
destination = destinationCandidate;
|
||||
}
|
||||
|
||||
final Uri uri = Uri.parse(destination);
|
||||
if ("file".equals(uri.getScheme())) {
|
||||
@ -115,8 +129,7 @@ public class AsyncDrawableLoader implements AsyncDrawable.Loader {
|
||||
|
||||
Drawable result = null;
|
||||
|
||||
if (item != null
|
||||
&& item.inputStream != null) {
|
||||
if (item != null && item.inputStream != null) {
|
||||
try {
|
||||
if (CONTENT_TYPE_SVG.equals(item.type)) {
|
||||
result = handleSvg(item.inputStream);
|
||||
@ -136,12 +149,17 @@ public class AsyncDrawableLoader implements AsyncDrawable.Loader {
|
||||
|
||||
// if result is null, we assume it's an error
|
||||
if (result == null) {
|
||||
result = errorDrawable;
|
||||
result = mErrorDrawable;
|
||||
}
|
||||
|
||||
if (result != null) {
|
||||
final Drawable out = result;
|
||||
mainThread.post(new Runnable() {
|
||||
final Drawable out;
|
||||
if (height != -1 && width != -1) {
|
||||
out = resize(result, height, width);
|
||||
} else {
|
||||
out = result;
|
||||
}
|
||||
mMainThread.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final AsyncDrawable asyncDrawable = reference.get();
|
||||
@ -152,16 +170,39 @@ public class AsyncDrawableLoader implements AsyncDrawable.Loader {
|
||||
});
|
||||
}
|
||||
|
||||
requests.remove(destination);
|
||||
mRequests.remove(destinationCandidate);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Drawable resize(Drawable image, int height, float width) {
|
||||
final float density = mResources.getDisplayMetrics().density;
|
||||
|
||||
if (width == 0 || height == 0) {
|
||||
final float imageDensity = (float) image.getIntrinsicWidth() / image.getIntrinsicHeight();
|
||||
if (width == 0) {
|
||||
width = intCeil(height * imageDensity);
|
||||
}
|
||||
if (height == 0) {
|
||||
height = intCeil(width / imageDensity);
|
||||
}
|
||||
}
|
||||
|
||||
final int widthPx = intCeil(width * density);
|
||||
final int heightPx = intCeil(height * density);
|
||||
|
||||
image.setBounds(0, 0, widthPx, heightPx);
|
||||
return image;
|
||||
}
|
||||
|
||||
private int intCeil(double d){
|
||||
return (int) Math.ceil(d);
|
||||
}
|
||||
|
||||
private Item fromFile(Uri uri) {
|
||||
|
||||
final List<String> segments = uri.getPathSegments();
|
||||
if (segments == null
|
||||
|| segments.size() == 0) {
|
||||
if (segments == null || segments.size() == 0) {
|
||||
// pointing to file & having no path segments is no use
|
||||
return null;
|
||||
}
|
||||
@ -192,7 +233,7 @@ public class AsyncDrawableLoader implements AsyncDrawable.Loader {
|
||||
// load assets
|
||||
InputStream inner = null;
|
||||
try {
|
||||
inner = resources.getAssets().open(path.toString());
|
||||
inner = mResources.getAssets().open(path.toString());
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
@ -227,7 +268,7 @@ public class AsyncDrawableLoader implements AsyncDrawable.Loader {
|
||||
|
||||
Response response = null;
|
||||
try {
|
||||
response = client.newCall(request).execute();
|
||||
response = mClient.newCall(request).execute();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
@ -239,8 +280,7 @@ public class AsyncDrawableLoader implements AsyncDrawable.Loader {
|
||||
if (inputStream != null) {
|
||||
final String type;
|
||||
final String contentType = response.header(HEADER_CONTENT_TYPE);
|
||||
if (!TextUtils.isEmpty(contentType)
|
||||
&& contentType.startsWith(CONTENT_TYPE_SVG)) {
|
||||
if (!TextUtils.isEmpty(contentType) && contentType.startsWith(CONTENT_TYPE_SVG)) {
|
||||
type = CONTENT_TYPE_SVG;
|
||||
} else {
|
||||
type = contentType;
|
||||
@ -270,7 +310,7 @@ public class AsyncDrawableLoader implements AsyncDrawable.Loader {
|
||||
|
||||
final float w = svg.getDocumentWidth();
|
||||
final float h = svg.getDocumentHeight();
|
||||
final float density = resources.getDisplayMetrics().density;
|
||||
final float density = mResources.getDisplayMetrics().density;
|
||||
|
||||
final int width = (int) (w * density + .5F);
|
||||
final int height = (int) (h * density + .5F);
|
||||
@ -280,7 +320,7 @@ public class AsyncDrawableLoader implements AsyncDrawable.Loader {
|
||||
canvas.scale(density, density);
|
||||
svg.renderToCanvas(canvas);
|
||||
|
||||
out = new BitmapDrawable(resources, bitmap);
|
||||
out = new BitmapDrawable(mResources, bitmap);
|
||||
DrawableUtils.intrinsicBounds(out);
|
||||
}
|
||||
|
||||
@ -310,7 +350,7 @@ public class AsyncDrawableLoader implements AsyncDrawable.Loader {
|
||||
|
||||
final Bitmap bitmap = BitmapFactory.decodeStream(stream);
|
||||
if (bitmap != null) {
|
||||
out = new BitmapDrawable(resources, bitmap);
|
||||
out = new BitmapDrawable(mResources, bitmap);
|
||||
DrawableUtils.intrinsicBounds(out);
|
||||
} else {
|
||||
out = null;
|
||||
|
@ -9,17 +9,17 @@ import ru.noties.markwon.view.IMarkwonView;
|
||||
|
||||
public class DebugConfigurationProvider implements IMarkwonView.ConfigurationProvider {
|
||||
|
||||
private SpannableConfiguration cached;
|
||||
private SpannableConfiguration mCached;
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public SpannableConfiguration provide(@NonNull Context context) {
|
||||
if (cached == null) {
|
||||
cached = SpannableConfiguration.builder(context)
|
||||
if (mCached == null) {
|
||||
mCached = SpannableConfiguration.builder(context)
|
||||
.theme(debugTheme(context))
|
||||
.build();
|
||||
}
|
||||
return cached;
|
||||
return mCached;
|
||||
}
|
||||
|
||||
private static SpannableTheme debugTheme(@NonNull Context context) {
|
||||
|
@ -12,7 +12,7 @@ import ru.noties.markwon.SpannableConfiguration;
|
||||
@SuppressLint("AppCompatCustomView")
|
||||
public class MarkwonView extends TextView implements IMarkwonView {
|
||||
|
||||
private MarkwonViewHelper helper;
|
||||
private MarkwonViewHelper mHelper;
|
||||
|
||||
public MarkwonView(Context context) {
|
||||
super(context);
|
||||
@ -25,26 +25,26 @@ public class MarkwonView extends TextView implements IMarkwonView {
|
||||
}
|
||||
|
||||
private void init(Context context, AttributeSet attributeSet) {
|
||||
helper = MarkwonViewHelper.create(this);
|
||||
helper.init(context, attributeSet);
|
||||
mHelper = MarkwonViewHelper.create(this);
|
||||
mHelper.init(context, attributeSet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setConfigurationProvider(@NonNull ConfigurationProvider provider) {
|
||||
helper.setConfigurationProvider(provider);
|
||||
mHelper.setConfigurationProvider(provider);
|
||||
}
|
||||
|
||||
public void setMarkdown(@Nullable String markdown) {
|
||||
helper.setMarkdown(markdown);
|
||||
mHelper.setMarkdown(markdown);
|
||||
}
|
||||
|
||||
public void setMarkdown(@Nullable SpannableConfiguration configuration, @Nullable String markdown) {
|
||||
helper.setMarkdown(configuration, markdown);
|
||||
mHelper.setMarkdown(configuration, markdown);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getMarkdown() {
|
||||
return helper.getMarkdown();
|
||||
return mHelper.getMarkdown();
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import ru.noties.markwon.SpannableConfiguration;
|
||||
|
||||
public class MarkwonViewCompat extends AppCompatTextView implements IMarkwonView {
|
||||
|
||||
private MarkwonViewHelper helper;
|
||||
private MarkwonViewHelper mHelper;
|
||||
|
||||
public MarkwonViewCompat(Context context) {
|
||||
super(context);
|
||||
@ -23,28 +23,28 @@ public class MarkwonViewCompat extends AppCompatTextView implements IMarkwonView
|
||||
}
|
||||
|
||||
private void init(Context context, AttributeSet attributeSet) {
|
||||
helper = MarkwonViewHelper.create(this);
|
||||
helper.init(context, attributeSet);
|
||||
mHelper = MarkwonViewHelper.create(this);
|
||||
mHelper.init(context, attributeSet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setConfigurationProvider(@NonNull ConfigurationProvider provider) {
|
||||
helper.setConfigurationProvider(provider);
|
||||
mHelper.setConfigurationProvider(provider);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMarkdown(@Nullable String markdown) {
|
||||
helper.setMarkdown(markdown);
|
||||
mHelper.setMarkdown(markdown);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMarkdown(@Nullable SpannableConfiguration configuration, @Nullable String markdown) {
|
||||
helper.setMarkdown(configuration, markdown);
|
||||
mHelper.setMarkdown(configuration, markdown);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getMarkdown() {
|
||||
return helper.getMarkdown();
|
||||
return mHelper.getMarkdown();
|
||||
}
|
||||
}
|
||||
|
@ -17,15 +17,15 @@ public class MarkwonViewHelper implements IMarkwonView {
|
||||
return new MarkwonViewHelper(view);
|
||||
}
|
||||
|
||||
private final TextView textView;
|
||||
private final TextView mTextView;
|
||||
|
||||
private ConfigurationProvider provider;
|
||||
private ConfigurationProvider mProvider;
|
||||
|
||||
private SpannableConfiguration configuration;
|
||||
private String markdown;
|
||||
private SpannableConfiguration mConfiguration;
|
||||
private String mMarkdown;
|
||||
|
||||
private MarkwonViewHelper(@NonNull TextView textView) {
|
||||
this.textView = textView;
|
||||
mTextView = textView;
|
||||
}
|
||||
|
||||
public void init(Context context, AttributeSet attributeSet) {
|
||||
@ -57,11 +57,11 @@ public class MarkwonViewHelper implements IMarkwonView {
|
||||
|
||||
@Override
|
||||
public void setConfigurationProvider(@NonNull ConfigurationProvider provider) {
|
||||
this.provider = provider;
|
||||
this.configuration = provider.provide(textView.getContext());
|
||||
if (!TextUtils.isEmpty(markdown)) {
|
||||
mProvider = provider;
|
||||
mConfiguration = provider.provide(mTextView.getContext());
|
||||
if (!TextUtils.isEmpty(mMarkdown)) {
|
||||
// invalidate rendered markdown
|
||||
setMarkdown(markdown);
|
||||
setMarkdown(mMarkdown);
|
||||
}
|
||||
}
|
||||
|
||||
@ -72,24 +72,24 @@ public class MarkwonViewHelper implements IMarkwonView {
|
||||
|
||||
@Override
|
||||
public void setMarkdown(@Nullable SpannableConfiguration configuration, @Nullable String markdown) {
|
||||
this.markdown = markdown;
|
||||
mMarkdown = markdown;
|
||||
if (configuration == null) {
|
||||
if (this.configuration == null) {
|
||||
if (provider != null) {
|
||||
this.configuration = provider.provide(textView.getContext());
|
||||
if (mConfiguration == null) {
|
||||
if (mProvider != null) {
|
||||
mConfiguration = mProvider.provide(mTextView.getContext());
|
||||
} else {
|
||||
this.configuration = SpannableConfiguration.create(textView.getContext());
|
||||
mConfiguration = SpannableConfiguration.create(mTextView.getContext());
|
||||
}
|
||||
}
|
||||
configuration = this.configuration;
|
||||
configuration = mConfiguration;
|
||||
}
|
||||
Markwon.setMarkdown(textView, configuration, markdown);
|
||||
Markwon.setMarkdown(mTextView, configuration, markdown);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getMarkdown() {
|
||||
return markdown;
|
||||
return mMarkdown;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
@ -0,0 +1,13 @@
|
||||
package ru.noties.markwon;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.view.View;
|
||||
|
||||
/**
|
||||
* @author pa.gulko zTrap (25.10.2017)
|
||||
* @since 1.0.1
|
||||
*/
|
||||
public interface ImageClickResolver {
|
||||
|
||||
void resolve(View view, @NonNull String link);
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package ru.noties.markwon;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* @author pa.gulko zTrap (25.10.2017)
|
||||
* @since 1.0.1
|
||||
*/
|
||||
public class ImageClickResolverDef implements ImageClickResolver {
|
||||
|
||||
@Override
|
||||
public void resolve(View view, @NonNull String link) {
|
||||
final Uri uri = Uri.parse(LinkUtils.cropImageSizes(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());
|
||||
}
|
||||
}
|
||||
}
|
@ -12,6 +12,7 @@ import android.view.View;
|
||||
import ru.noties.markwon.spans.LinkSpan;
|
||||
|
||||
public class LinkResolverDef implements LinkSpan.Resolver {
|
||||
|
||||
@Override
|
||||
public void resolve(View view, @NonNull String link) {
|
||||
final Uri uri = Uri.parse(link);
|
||||
|
26
library/src/main/java/ru/noties/markwon/LinkUtils.java
Normal file
26
library/src/main/java/ru/noties/markwon/LinkUtils.java
Normal file
@ -0,0 +1,26 @@
|
||||
package ru.noties.markwon;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* @author pa.gulko zTrap (26.10.2017)
|
||||
* @since 1.0.1
|
||||
*/
|
||||
public class LinkUtils {
|
||||
|
||||
private LinkUtils() {
|
||||
//no instance
|
||||
}
|
||||
|
||||
public static String cropImageSizes(@NonNull String link){
|
||||
Matcher matcher = Pattern.compile("(.*)/(\\d+)\\$(\\d+)").matcher(link);
|
||||
if (matcher.matches()){
|
||||
return matcher.group(1);
|
||||
} else {
|
||||
return link;
|
||||
}
|
||||
}
|
||||
}
|
@ -20,44 +20,50 @@ public class SpannableConfiguration {
|
||||
return new Builder(context);
|
||||
}
|
||||
|
||||
private final SpannableTheme theme;
|
||||
private final AsyncDrawable.Loader asyncDrawableLoader;
|
||||
private final SyntaxHighlight syntaxHighlight;
|
||||
private final LinkSpan.Resolver linkResolver;
|
||||
private final UrlProcessor urlProcessor;
|
||||
private final SpannableHtmlParser htmlParser;
|
||||
private final SpannableTheme mTheme;
|
||||
private final AsyncDrawable.Loader mAsyncDrawableLoader;
|
||||
private final SyntaxHighlight mSyntaxHighlight;
|
||||
private final LinkSpan.Resolver mLinkResolver;
|
||||
private final UrlProcessor mUrlProcessor;
|
||||
private final SpannableHtmlParser mHtmlParser;
|
||||
private final ImageClickResolver mImageClickResolver;
|
||||
|
||||
private SpannableConfiguration(Builder builder) {
|
||||
this.theme = builder.theme;
|
||||
this.asyncDrawableLoader = builder.asyncDrawableLoader;
|
||||
this.syntaxHighlight = builder.syntaxHighlight;
|
||||
this.linkResolver = builder.linkResolver;
|
||||
this.urlProcessor = builder.urlProcessor;
|
||||
this.htmlParser = builder.htmlParser;
|
||||
mTheme = builder.theme;
|
||||
mAsyncDrawableLoader = builder.asyncDrawableLoader;
|
||||
mSyntaxHighlight = builder.syntaxHighlight;
|
||||
mLinkResolver = builder.linkResolver;
|
||||
mUrlProcessor = builder.urlProcessor;
|
||||
mHtmlParser = builder.htmlParser;
|
||||
mImageClickResolver = builder.imageClickResolver;
|
||||
}
|
||||
|
||||
public SpannableTheme theme() {
|
||||
return theme;
|
||||
return mTheme;
|
||||
}
|
||||
|
||||
public AsyncDrawable.Loader asyncDrawableLoader() {
|
||||
return asyncDrawableLoader;
|
||||
return mAsyncDrawableLoader;
|
||||
}
|
||||
|
||||
public SyntaxHighlight syntaxHighlight() {
|
||||
return syntaxHighlight;
|
||||
return mSyntaxHighlight;
|
||||
}
|
||||
|
||||
public LinkSpan.Resolver linkResolver() {
|
||||
return linkResolver;
|
||||
return mLinkResolver;
|
||||
}
|
||||
|
||||
public UrlProcessor urlProcessor() {
|
||||
return urlProcessor;
|
||||
return mUrlProcessor;
|
||||
}
|
||||
|
||||
public SpannableHtmlParser htmlParser() {
|
||||
return htmlParser;
|
||||
return mHtmlParser;
|
||||
}
|
||||
|
||||
public ImageClickResolver imageClickResolver() {
|
||||
return mImageClickResolver;
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
@ -69,6 +75,7 @@ public class SpannableConfiguration {
|
||||
private LinkSpan.Resolver linkResolver;
|
||||
private UrlProcessor urlProcessor;
|
||||
private SpannableHtmlParser htmlParser;
|
||||
private ImageClickResolver imageClickResolver;
|
||||
|
||||
Builder(Context context) {
|
||||
this.context = context;
|
||||
@ -104,6 +111,11 @@ public class SpannableConfiguration {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setImageClickResolver(ImageClickResolver imageClickResolver) {
|
||||
this.imageClickResolver = imageClickResolver;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SpannableConfiguration build() {
|
||||
if (theme == null) {
|
||||
theme = SpannableTheme.create(context);
|
||||
@ -120,8 +132,12 @@ public class SpannableConfiguration {
|
||||
if (urlProcessor == null) {
|
||||
urlProcessor = new UrlProcessorNoOp();
|
||||
}
|
||||
if (imageClickResolver == null) {
|
||||
imageClickResolver = new ImageClickResolverDef();
|
||||
}
|
||||
if (htmlParser == null) {
|
||||
htmlParser = SpannableHtmlParser.create(theme, asyncDrawableLoader, urlProcessor, linkResolver);
|
||||
htmlParser = SpannableHtmlParser.create(theme, asyncDrawableLoader, urlProcessor,
|
||||
linkResolver, imageClickResolver);
|
||||
}
|
||||
return new SpannableConfiguration(this);
|
||||
}
|
||||
|
@ -13,8 +13,7 @@ abstract class TableRowsScheduler {
|
||||
|
||||
static void schedule(@NonNull final TextView view) {
|
||||
final Object[] spans = extract(view);
|
||||
if (spans != null
|
||||
&& spans.length > 0) {
|
||||
if (spans != null && spans.length > 0) {
|
||||
|
||||
if (view.getTag(R.id.markwon_tables_scheduler) == null) {
|
||||
final View.OnAttachStateChangeListener listener = new View.OnAttachStateChangeListener() {
|
||||
|
@ -7,17 +7,17 @@ import android.text.TextUtils;
|
||||
|
||||
public class UrlProcessorAndroidAssets implements UrlProcessor {
|
||||
|
||||
private final UrlProcessorRelativeToAbsolute assetsProcessor
|
||||
private final UrlProcessorRelativeToAbsolute mAssetsProcessor
|
||||
= new UrlProcessorRelativeToAbsolute("file:///android_asset/");
|
||||
|
||||
private final UrlProcessor processor;
|
||||
private final UrlProcessor mProcessor;
|
||||
|
||||
public UrlProcessorAndroidAssets() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
public UrlProcessorAndroidAssets(@Nullable UrlProcessor parent) {
|
||||
this.processor = parent;
|
||||
mProcessor = parent;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@ -26,10 +26,10 @@ public class UrlProcessorAndroidAssets implements UrlProcessor {
|
||||
final String out;
|
||||
final Uri uri = Uri.parse(destination);
|
||||
if (TextUtils.isEmpty(uri.getScheme())) {
|
||||
out = assetsProcessor.process(destination);
|
||||
out = mAssetsProcessor.process(destination);
|
||||
} else {
|
||||
if (processor != null) {
|
||||
out = processor.process(destination);
|
||||
if (mProcessor != null) {
|
||||
out = mProcessor.process(destination);
|
||||
} else {
|
||||
out = destination;
|
||||
}
|
||||
|
@ -9,10 +9,10 @@ import java.net.URL;
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public class UrlProcessorRelativeToAbsolute implements UrlProcessor {
|
||||
|
||||
private final URL base;
|
||||
private final URL mBase;
|
||||
|
||||
public UrlProcessorRelativeToAbsolute(@NonNull String base) {
|
||||
this.base = obtain(base);
|
||||
mBase = obtain(base);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@ -21,9 +21,9 @@ public class UrlProcessorRelativeToAbsolute implements UrlProcessor {
|
||||
|
||||
String out = destination;
|
||||
|
||||
if (base != null) {
|
||||
if (mBase != null) {
|
||||
try {
|
||||
final URL u = new URL(base, destination);
|
||||
final URL u = new URL(mBase, destination);
|
||||
out = u.toString();
|
||||
} catch (MalformedURLException e) {
|
||||
e.printStackTrace();
|
||||
|
@ -4,7 +4,9 @@ import android.support.annotation.NonNull;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.Spanned;
|
||||
import android.text.TextUtils;
|
||||
import android.text.style.ClickableSpan;
|
||||
import android.text.style.StrikethroughSpan;
|
||||
import android.view.View;
|
||||
|
||||
import org.commonmark.ext.gfm.strikethrough.Strikethrough;
|
||||
import org.commonmark.ext.gfm.tables.TableBody;
|
||||
@ -60,41 +62,41 @@ import ru.noties.markwon.tasklist.TaskListItem;
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public class SpannableMarkdownVisitor extends AbstractVisitor {
|
||||
|
||||
private final SpannableConfiguration configuration;
|
||||
private final SpannableStringBuilder builder;
|
||||
private final Deque<HtmlInlineItem> htmlInlineItems;
|
||||
private final SpannableConfiguration mConfiguration;
|
||||
private final SpannableStringBuilder mBuilder;
|
||||
private final Deque<HtmlInlineItem> mHtmlInlineItems;
|
||||
|
||||
private int blockQuoteIndent;
|
||||
private int listLevel;
|
||||
private int mBlockQuoteIndent;
|
||||
private int mListLevel;
|
||||
|
||||
private List<TableRowSpan.Cell> pendingTableRow;
|
||||
private boolean tableRowIsHeader;
|
||||
private int tableRows;
|
||||
private List<TableRowSpan.Cell> mPendingTableRow;
|
||||
private boolean mTableRowIsHeader;
|
||||
private int mTableRows;
|
||||
|
||||
public SpannableMarkdownVisitor(
|
||||
@NonNull SpannableConfiguration configuration,
|
||||
@NonNull SpannableStringBuilder builder
|
||||
) {
|
||||
this.configuration = configuration;
|
||||
this.builder = builder;
|
||||
this.htmlInlineItems = new ArrayDeque<>(2);
|
||||
mConfiguration = configuration;
|
||||
mBuilder = builder;
|
||||
mHtmlInlineItems = new ArrayDeque<>(2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(Text text) {
|
||||
builder.append(text.getLiteral());
|
||||
mBuilder.append(text.getLiteral());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(StrongEmphasis strongEmphasis) {
|
||||
final int length = builder.length();
|
||||
final int length = mBuilder.length();
|
||||
visitChildren(strongEmphasis);
|
||||
setSpan(length, new StrongEmphasisSpan());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(Emphasis emphasis) {
|
||||
final int length = builder.length();
|
||||
final int length = mBuilder.length();
|
||||
visitChildren(emphasis);
|
||||
setSpan(length, new EmphasisSpan());
|
||||
}
|
||||
@ -103,42 +105,42 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
|
||||
public void visit(BlockQuote blockQuote) {
|
||||
|
||||
newLine();
|
||||
if (blockQuoteIndent != 0) {
|
||||
builder.append('\n');
|
||||
if (mBlockQuoteIndent != 0) {
|
||||
mBuilder.append('\n');
|
||||
}
|
||||
|
||||
final int length = builder.length();
|
||||
final int length = mBuilder.length();
|
||||
|
||||
blockQuoteIndent += 1;
|
||||
mBlockQuoteIndent += 1;
|
||||
|
||||
visitChildren(blockQuote);
|
||||
|
||||
setSpan(length, new BlockQuoteSpan(
|
||||
configuration.theme(),
|
||||
blockQuoteIndent
|
||||
mConfiguration.theme(),
|
||||
mBlockQuoteIndent
|
||||
));
|
||||
|
||||
blockQuoteIndent -= 1;
|
||||
mBlockQuoteIndent -= 1;
|
||||
|
||||
newLine();
|
||||
if (blockQuoteIndent == 0) {
|
||||
builder.append('\n');
|
||||
if (mBlockQuoteIndent == 0) {
|
||||
mBuilder.append('\n');
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(Code code) {
|
||||
|
||||
final int length = builder.length();
|
||||
final int length = mBuilder.length();
|
||||
|
||||
// NB, in order to provide a _padding_ feeling code is wrapped inside two unbreakable spaces
|
||||
// unfortunately we cannot use this for multiline code as we cannot control where a new line break will be inserted
|
||||
builder.append('\u00a0');
|
||||
builder.append(code.getLiteral());
|
||||
builder.append('\u00a0');
|
||||
mBuilder.append('\u00a0');
|
||||
mBuilder.append(code.getLiteral());
|
||||
mBuilder.append('\u00a0');
|
||||
|
||||
setSpan(length, new CodeSpan(
|
||||
configuration.theme(),
|
||||
mConfiguration.theme(),
|
||||
false
|
||||
));
|
||||
}
|
||||
@ -148,23 +150,23 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
|
||||
|
||||
newLine();
|
||||
|
||||
final int length = builder.length();
|
||||
final int length = mBuilder.length();
|
||||
|
||||
// empty lines on top & bottom
|
||||
builder.append('\u00a0').append('\n');
|
||||
builder.append(
|
||||
configuration.syntaxHighlight()
|
||||
mBuilder.append('\u00a0').append('\n');
|
||||
mBuilder.append(
|
||||
mConfiguration.syntaxHighlight()
|
||||
.highlight(fencedCodeBlock.getInfo(), fencedCodeBlock.getLiteral())
|
||||
);
|
||||
builder.append('\u00a0').append('\n');
|
||||
mBuilder.append('\u00a0').append('\n');
|
||||
|
||||
setSpan(length, new CodeSpan(
|
||||
configuration.theme(),
|
||||
mConfiguration.theme(),
|
||||
true
|
||||
));
|
||||
|
||||
newLine();
|
||||
builder.append('\n');
|
||||
mBuilder.append('\n');
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -181,18 +183,18 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
|
||||
newLine();
|
||||
visitChildren(node);
|
||||
newLine();
|
||||
if (listLevel == 0 && blockQuoteIndent == 0) {
|
||||
builder.append('\n');
|
||||
if (mListLevel == 0 && mBlockQuoteIndent == 0) {
|
||||
mBuilder.append('\n');
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(ListItem listItem) {
|
||||
|
||||
final int length = builder.length();
|
||||
final int length = mBuilder.length();
|
||||
|
||||
blockQuoteIndent += 1;
|
||||
listLevel += 1;
|
||||
mBlockQuoteIndent += 1;
|
||||
mListLevel += 1;
|
||||
|
||||
final Node parent = listItem.getParent();
|
||||
if (parent instanceof OrderedList) {
|
||||
@ -202,9 +204,9 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
|
||||
visitChildren(listItem);
|
||||
|
||||
setSpan(length, new OrderedListItemSpan(
|
||||
configuration.theme(),
|
||||
mConfiguration.theme(),
|
||||
String.valueOf(start) + "." + '\u00a0',
|
||||
blockQuoteIndent
|
||||
mBlockQuoteIndent
|
||||
));
|
||||
|
||||
// after we have visited the children increment start number
|
||||
@ -216,14 +218,14 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
|
||||
visitChildren(listItem);
|
||||
|
||||
setSpan(length, new BulletListItemSpan(
|
||||
configuration.theme(),
|
||||
blockQuoteIndent,
|
||||
listLevel - 1
|
||||
mConfiguration.theme(),
|
||||
mBlockQuoteIndent,
|
||||
mListLevel - 1
|
||||
));
|
||||
}
|
||||
|
||||
blockQuoteIndent -= 1;
|
||||
listLevel -= 1;
|
||||
mBlockQuoteIndent -= 1;
|
||||
mListLevel -= 1;
|
||||
|
||||
newLine();
|
||||
}
|
||||
@ -233,12 +235,12 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
|
||||
|
||||
newLine();
|
||||
|
||||
final int length = builder.length();
|
||||
builder.append(' '); // without space it won't render
|
||||
setSpan(length, new ThematicBreakSpan(configuration.theme()));
|
||||
final int length = mBuilder.length();
|
||||
mBuilder.append(' '); // without space it won't render
|
||||
setSpan(length, new ThematicBreakSpan(mConfiguration.theme()));
|
||||
|
||||
newLine();
|
||||
builder.append('\n');
|
||||
mBuilder.append('\n');
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -246,24 +248,24 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
|
||||
|
||||
newLine();
|
||||
|
||||
final int length = builder.length();
|
||||
final int length = mBuilder.length();
|
||||
visitChildren(heading);
|
||||
setSpan(length, new HeadingSpan(
|
||||
configuration.theme(),
|
||||
mConfiguration.theme(),
|
||||
heading.getLevel(),
|
||||
builder.length() - length)
|
||||
mBuilder.length() - length)
|
||||
);
|
||||
|
||||
newLine();
|
||||
|
||||
// after heading we add another line anyway (no additional checks)
|
||||
builder.append('\n');
|
||||
mBuilder.append('\n');
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(SoftLineBreak softLineBreak) {
|
||||
// at first here was a new line, but here should be a space char
|
||||
builder.append(' ');
|
||||
mBuilder.append(' ');
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -277,11 +279,11 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
|
||||
@Override
|
||||
public void visit(CustomBlock customBlock) {
|
||||
if (customBlock instanceof TaskListBlock) {
|
||||
blockQuoteIndent += 1;
|
||||
mBlockQuoteIndent += 1;
|
||||
visitChildren(customBlock);
|
||||
blockQuoteIndent -= 1;
|
||||
mBlockQuoteIndent -= 1;
|
||||
newLine();
|
||||
builder.append('\n');
|
||||
mBuilder.append('\n');
|
||||
} else {
|
||||
super.visit(customBlock);
|
||||
}
|
||||
@ -292,7 +294,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
|
||||
|
||||
if (customNode instanceof Strikethrough) {
|
||||
|
||||
final int length = builder.length();
|
||||
final int length = mBuilder.length();
|
||||
visitChildren(customNode);
|
||||
setSpan(length, new StrikethroughSpan());
|
||||
|
||||
@ -302,22 +304,22 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
|
||||
|
||||
final TaskListItem listItem = (TaskListItem) customNode;
|
||||
|
||||
final int length = builder.length();
|
||||
final int length = mBuilder.length();
|
||||
|
||||
blockQuoteIndent += listItem.indent();
|
||||
mBlockQuoteIndent += listItem.indent();
|
||||
|
||||
visitChildren(customNode);
|
||||
|
||||
setSpan(length, new TaskListSpan(
|
||||
configuration.theme(),
|
||||
blockQuoteIndent,
|
||||
mConfiguration.theme(),
|
||||
mBlockQuoteIndent,
|
||||
length,
|
||||
listItem.done()
|
||||
));
|
||||
|
||||
newLine();
|
||||
|
||||
blockQuoteIndent -= listItem.indent();
|
||||
mBlockQuoteIndent -= listItem.indent();
|
||||
|
||||
} else if (!handleTableNodes(customNode)) {
|
||||
super.visit(customNode);
|
||||
@ -330,50 +332,50 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
|
||||
|
||||
if (node instanceof TableBody) {
|
||||
visitChildren(node);
|
||||
tableRows = 0;
|
||||
mTableRows = 0;
|
||||
handled = true;
|
||||
newLine();
|
||||
builder.append('\n');
|
||||
mBuilder.append('\n');
|
||||
} else if (node instanceof TableRow) {
|
||||
|
||||
final int length = builder.length();
|
||||
final int length = mBuilder.length();
|
||||
visitChildren(node);
|
||||
|
||||
if (pendingTableRow != null) {
|
||||
builder.append(' ');
|
||||
if (mPendingTableRow != null) {
|
||||
mBuilder.append(' ');
|
||||
|
||||
final TableRowSpan span = new TableRowSpan(
|
||||
configuration.theme(),
|
||||
pendingTableRow,
|
||||
tableRowIsHeader,
|
||||
tableRows % 2 == 1
|
||||
mConfiguration.theme(),
|
||||
mPendingTableRow,
|
||||
mTableRowIsHeader,
|
||||
mTableRows % 2 == 1
|
||||
);
|
||||
|
||||
tableRows = tableRowIsHeader
|
||||
mTableRows = mTableRowIsHeader
|
||||
? 0
|
||||
: tableRows + 1;
|
||||
: mTableRows + 1;
|
||||
|
||||
setSpan(length, span);
|
||||
newLine();
|
||||
pendingTableRow = null;
|
||||
mPendingTableRow = null;
|
||||
}
|
||||
|
||||
handled = true;
|
||||
} else if (node instanceof TableCell) {
|
||||
|
||||
final TableCell cell = (TableCell) node;
|
||||
final int length = builder.length();
|
||||
final int length = mBuilder.length();
|
||||
visitChildren(cell);
|
||||
if (pendingTableRow == null) {
|
||||
pendingTableRow = new ArrayList<>(2);
|
||||
if (mPendingTableRow == null) {
|
||||
mPendingTableRow = new ArrayList<>(2);
|
||||
}
|
||||
pendingTableRow.add(new TableRowSpan.Cell(
|
||||
mPendingTableRow.add(new TableRowSpan.Cell(
|
||||
tableCellAlignment(cell.getAlignment()),
|
||||
builder.subSequence(length, builder.length())
|
||||
mBuilder.subSequence(length, mBuilder.length())
|
||||
));
|
||||
builder.replace(length, builder.length(), "");
|
||||
mBuilder.replace(length, mBuilder.length(), "");
|
||||
|
||||
tableRowIsHeader = cell.isHeader();
|
||||
mTableRowIsHeader = cell.isHeader();
|
||||
|
||||
handled = true;
|
||||
} else {
|
||||
@ -396,8 +398,8 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
|
||||
if (!inTightList) {
|
||||
newLine();
|
||||
|
||||
if (blockQuoteIndent == 0) {
|
||||
builder.append('\n');
|
||||
if (mBlockQuoteIndent == 0) {
|
||||
mBuilder.append('\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -405,49 +407,53 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
|
||||
@Override
|
||||
public void visit(Image image) {
|
||||
|
||||
final int length = builder.length();
|
||||
final int length = mBuilder.length();
|
||||
|
||||
visitChildren(image);
|
||||
|
||||
// we must check if anything _was_ added, as we need at least one char to render
|
||||
if (length == builder.length()) {
|
||||
builder.append('\uFFFC');
|
||||
if (length == mBuilder.length()) {
|
||||
mBuilder.append('\uFFFC');
|
||||
}
|
||||
|
||||
final Node parent = image.getParent();
|
||||
final boolean link = parent != null && parent instanceof Link;
|
||||
final String destination = configuration.urlProcessor().process(image.getDestination());
|
||||
final String destination = mConfiguration.urlProcessor().process(image.getDestination());
|
||||
|
||||
setSpan(
|
||||
length,
|
||||
new AsyncDrawableSpan(
|
||||
configuration.theme(),
|
||||
mConfiguration.theme(),
|
||||
new AsyncDrawable(
|
||||
destination,
|
||||
configuration.asyncDrawableLoader()
|
||||
mConfiguration.asyncDrawableLoader()
|
||||
),
|
||||
AsyncDrawableSpan.ALIGN_BOTTOM,
|
||||
link
|
||||
)
|
||||
);
|
||||
|
||||
// todo, maybe, if image is not inside a link, we should make it clickable, so
|
||||
// user can open it in external viewer?
|
||||
setSpan(length, new ClickableSpan() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
mConfiguration.imageClickResolver().resolve(view, destination);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(HtmlBlock htmlBlock) {
|
||||
// http://spec.commonmark.org/0.18/#html-blocks
|
||||
final Spanned spanned = configuration.htmlParser().getSpanned(null, htmlBlock.getLiteral());
|
||||
final Spanned spanned = mConfiguration.htmlParser().getSpanned(null, htmlBlock.getLiteral());
|
||||
if (!TextUtils.isEmpty(spanned)) {
|
||||
builder.append(spanned);
|
||||
mBuilder.append(spanned);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(HtmlInline htmlInline) {
|
||||
|
||||
final SpannableHtmlParser htmlParser = configuration.htmlParser();
|
||||
final SpannableHtmlParser htmlParser = mConfiguration.htmlParser();
|
||||
final SpannableHtmlParser.Tag tag = htmlParser.parseTag(htmlInline.getLiteral());
|
||||
|
||||
if (tag != null) {
|
||||
@ -455,13 +461,13 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
|
||||
final boolean voidTag = tag.voidTag();
|
||||
if (!voidTag && tag.opening()) {
|
||||
// push in stack
|
||||
htmlInlineItems.push(new HtmlInlineItem(tag, builder.length()));
|
||||
mHtmlInlineItems.push(new HtmlInlineItem(tag, mBuilder.length()));
|
||||
visitChildren(htmlInline);
|
||||
} else {
|
||||
|
||||
if (!voidTag) {
|
||||
if (htmlInlineItems.size() > 0) {
|
||||
final HtmlInlineItem item = htmlInlineItems.pop();
|
||||
if (mHtmlInlineItems.size() > 0) {
|
||||
final HtmlInlineItem item = mHtmlInlineItems.pop();
|
||||
final Object span = htmlParser.getSpanForTag(item.tag);
|
||||
if (span != null) {
|
||||
setSpan(item.start, span);
|
||||
@ -471,34 +477,34 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
|
||||
|
||||
final Spanned html = htmlParser.getSpanned(tag, htmlInline.getLiteral());
|
||||
if (!TextUtils.isEmpty(html)) {
|
||||
builder.append(html);
|
||||
mBuilder.append(html);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// todo, should we append just literal?
|
||||
// builder.append(htmlInline.getLiteral());
|
||||
// mBuilder.append(htmlInline.getLiteral());
|
||||
visitChildren(htmlInline);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(Link link) {
|
||||
final int length = builder.length();
|
||||
final int length = mBuilder.length();
|
||||
visitChildren(link);
|
||||
final String destination = configuration.urlProcessor().process(link.getDestination());
|
||||
setSpan(length, new LinkSpan(configuration.theme(), destination, configuration.linkResolver()));
|
||||
final String destination = mConfiguration.urlProcessor().process(link.getDestination());
|
||||
setSpan(length, new LinkSpan(mConfiguration.theme(), destination, mConfiguration.linkResolver()));
|
||||
}
|
||||
|
||||
private void setSpan(int start, @NonNull Object span) {
|
||||
builder.setSpan(span, start, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
mBuilder.setSpan(span, start, mBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
|
||||
private void newLine() {
|
||||
if (builder.length() > 0
|
||||
&& '\n' != builder.charAt(builder.length() - 1)) {
|
||||
builder.append('\n');
|
||||
if (mBuilder.length() > 0
|
||||
&& '\n' != mBuilder.charAt(mBuilder.length() - 1)) {
|
||||
mBuilder.append('\n');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,9 +4,12 @@ import android.support.annotation.NonNull;
|
||||
import android.text.SpannableString;
|
||||
import android.text.Spanned;
|
||||
import android.text.TextUtils;
|
||||
import android.text.style.ClickableSpan;
|
||||
import android.view.View;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import ru.noties.markwon.ImageClickResolver;
|
||||
import ru.noties.markwon.UrlProcessor;
|
||||
import ru.noties.markwon.spans.AsyncDrawable;
|
||||
import ru.noties.markwon.spans.AsyncDrawableSpan;
|
||||
@ -14,17 +17,20 @@ import ru.noties.markwon.spans.SpannableTheme;
|
||||
|
||||
class ImageProviderImpl implements SpannableHtmlParser.ImageProvider {
|
||||
|
||||
private final SpannableTheme theme;
|
||||
private final AsyncDrawable.Loader loader;
|
||||
private final UrlProcessor urlProcessor;
|
||||
private final SpannableTheme mTheme;
|
||||
private final AsyncDrawable.Loader mLoader;
|
||||
private final UrlProcessor mUrlProcessor;
|
||||
private final ImageClickResolver mImageClickResolver;
|
||||
|
||||
ImageProviderImpl(
|
||||
@NonNull SpannableTheme theme,
|
||||
@NonNull AsyncDrawable.Loader loader,
|
||||
@NonNull UrlProcessor urlProcessor) {
|
||||
this.theme = theme;
|
||||
this.loader = loader;
|
||||
this.urlProcessor = urlProcessor;
|
||||
@NonNull UrlProcessor urlProcessor,
|
||||
@NonNull ImageClickResolver imageClickResolver) {
|
||||
mTheme = theme;
|
||||
mLoader = loader;
|
||||
mUrlProcessor = urlProcessor;
|
||||
mImageClickResolver = imageClickResolver;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -38,7 +44,7 @@ class ImageProviderImpl implements SpannableHtmlParser.ImageProvider {
|
||||
|
||||
if (!TextUtils.isEmpty(src)) {
|
||||
|
||||
final String destination = urlProcessor.process(src);
|
||||
final String destination = mUrlProcessor.process(src);
|
||||
|
||||
final String replacement;
|
||||
if (!TextUtils.isEmpty(alt)) {
|
||||
@ -47,12 +53,20 @@ class ImageProviderImpl implements SpannableHtmlParser.ImageProvider {
|
||||
replacement = "\uFFFC";
|
||||
}
|
||||
|
||||
final AsyncDrawable drawable = new AsyncDrawable(destination, loader);
|
||||
final AsyncDrawableSpan span = new AsyncDrawableSpan(theme, drawable);
|
||||
final AsyncDrawable drawable = new AsyncDrawable(destination, mLoader);
|
||||
final AsyncDrawableSpan span = new AsyncDrawableSpan(mTheme, drawable);
|
||||
|
||||
final SpannableString string = new SpannableString(replacement);
|
||||
string.setSpan(span, 0, string.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
|
||||
final ClickableSpan clickableSpan = new ClickableSpan() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
mImageClickResolver.resolve(view, destination);
|
||||
}
|
||||
};
|
||||
string.setSpan(clickableSpan, 0, string.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
|
||||
spanned = string;
|
||||
} else {
|
||||
spanned = null;
|
||||
|
@ -11,17 +11,17 @@ import ru.noties.markwon.spans.SpannableTheme;
|
||||
|
||||
class LinkProvider implements SpannableHtmlParser.SpanProvider {
|
||||
|
||||
private final SpannableTheme theme;
|
||||
private final UrlProcessor urlProcessor;
|
||||
private final LinkSpan.Resolver resolver;
|
||||
private final SpannableTheme mTheme;
|
||||
private final UrlProcessor mUrlProcessor;
|
||||
private final LinkSpan.Resolver mResolver;
|
||||
|
||||
LinkProvider(
|
||||
@NonNull SpannableTheme theme,
|
||||
@NonNull UrlProcessor urlProcessor,
|
||||
@NonNull LinkSpan.Resolver resolver) {
|
||||
this.theme = theme;
|
||||
this.urlProcessor = urlProcessor;
|
||||
this.resolver = resolver;
|
||||
mTheme = theme;
|
||||
mUrlProcessor = urlProcessor;
|
||||
mResolver = resolver;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -33,8 +33,8 @@ class LinkProvider implements SpannableHtmlParser.SpanProvider {
|
||||
final String href = attributes.get("href");
|
||||
if (!TextUtils.isEmpty(href)) {
|
||||
|
||||
final String destination = urlProcessor.process(href);
|
||||
span = new LinkSpan(theme, destination, resolver);
|
||||
final String destination = mUrlProcessor.process(href);
|
||||
span = new LinkSpan(mTheme, destination, mResolver);
|
||||
|
||||
} else {
|
||||
span = null;
|
||||
|
@ -10,6 +10,8 @@ import android.text.Spanned;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import ru.noties.markwon.ImageClickResolver;
|
||||
import ru.noties.markwon.ImageClickResolverDef;
|
||||
import ru.noties.markwon.LinkResolverDef;
|
||||
import ru.noties.markwon.UrlProcessor;
|
||||
import ru.noties.markwon.UrlProcessorNoOp;
|
||||
@ -25,7 +27,8 @@ public class SpannableHtmlParser {
|
||||
@NonNull SpannableTheme theme,
|
||||
@NonNull AsyncDrawable.Loader loader
|
||||
) {
|
||||
return builderWithDefaults(theme, loader, null, null)
|
||||
return builderWithDefaults(theme, loader, null, null,
|
||||
null)
|
||||
.build();
|
||||
}
|
||||
|
||||
@ -33,9 +36,10 @@ public class SpannableHtmlParser {
|
||||
@NonNull SpannableTheme theme,
|
||||
@NonNull AsyncDrawable.Loader loader,
|
||||
@NonNull UrlProcessor urlProcessor,
|
||||
@NonNull LinkSpan.Resolver resolver
|
||||
@NonNull LinkSpan.Resolver linkResolver,
|
||||
@NonNull ImageClickResolver imageClickResolver
|
||||
) {
|
||||
return builderWithDefaults(theme, loader, urlProcessor, resolver)
|
||||
return builderWithDefaults(theme, loader, urlProcessor, linkResolver, imageClickResolver)
|
||||
.build();
|
||||
}
|
||||
|
||||
@ -44,22 +48,28 @@ public class SpannableHtmlParser {
|
||||
}
|
||||
|
||||
public static Builder builderWithDefaults(@NonNull SpannableTheme theme) {
|
||||
return builderWithDefaults(theme, null, null, null);
|
||||
return builderWithDefaults(theme, null, null, null,
|
||||
null);
|
||||
}
|
||||
|
||||
public static Builder builderWithDefaults(
|
||||
@NonNull SpannableTheme theme,
|
||||
@Nullable AsyncDrawable.Loader asyncDrawableLoader,
|
||||
@Nullable UrlProcessor urlProcessor,
|
||||
@Nullable LinkSpan.Resolver resolver
|
||||
@Nullable LinkSpan.Resolver linkResolver,
|
||||
@Nullable ImageClickResolver imageClickResolver
|
||||
) {
|
||||
|
||||
if (urlProcessor == null) {
|
||||
urlProcessor = new UrlProcessorNoOp();
|
||||
}
|
||||
|
||||
if (resolver == null) {
|
||||
resolver = new LinkResolverDef();
|
||||
if (linkResolver == null) {
|
||||
linkResolver = new LinkResolverDef();
|
||||
}
|
||||
|
||||
if (imageClickResolver == null) {
|
||||
imageClickResolver = new ImageClickResolverDef();
|
||||
}
|
||||
|
||||
final BoldProvider boldProvider = new BoldProvider();
|
||||
@ -68,7 +78,8 @@ public class SpannableHtmlParser {
|
||||
|
||||
final ImageProvider imageProvider;
|
||||
if (asyncDrawableLoader != null) {
|
||||
imageProvider = new ImageProviderImpl(theme, asyncDrawableLoader, urlProcessor);
|
||||
imageProvider = new ImageProviderImpl(theme, asyncDrawableLoader, urlProcessor,
|
||||
imageClickResolver);
|
||||
} else {
|
||||
imageProvider = null;
|
||||
}
|
||||
@ -86,7 +97,7 @@ public class SpannableHtmlParser {
|
||||
.simpleTag("del", strikeProvider)
|
||||
.simpleTag("s", strikeProvider)
|
||||
.simpleTag("strike", strikeProvider)
|
||||
.simpleTag("a", new LinkProvider(theme, urlProcessor, resolver))
|
||||
.simpleTag("a", new LinkProvider(theme, urlProcessor, linkResolver))
|
||||
.imageProvider(imageProvider);
|
||||
}
|
||||
|
||||
|
@ -7,14 +7,14 @@ import ru.noties.markwon.spans.SubScriptSpan;
|
||||
|
||||
class SubScriptProvider implements SpannableHtmlParser.SpanProvider {
|
||||
|
||||
private final SpannableTheme theme;
|
||||
private final SpannableTheme mTheme;
|
||||
|
||||
public SubScriptProvider(SpannableTheme theme) {
|
||||
this.theme = theme;
|
||||
mTheme = theme;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object provide(@NonNull SpannableHtmlParser.Tag tag) {
|
||||
return new SubScriptSpan(theme);
|
||||
return new SubScriptSpan(mTheme);
|
||||
}
|
||||
}
|
||||
|
@ -7,14 +7,14 @@ import ru.noties.markwon.spans.SuperScriptSpan;
|
||||
|
||||
class SuperScriptProvider implements SpannableHtmlParser.SpanProvider {
|
||||
|
||||
private final SpannableTheme theme;
|
||||
private final SpannableTheme mTheme;
|
||||
|
||||
SuperScriptProvider(SpannableTheme theme) {
|
||||
this.theme = theme;
|
||||
mTheme = theme;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object provide(@NonNull SpannableHtmlParser.Tag tag) {
|
||||
return new SuperScriptSpan(theme);
|
||||
return new SuperScriptSpan(mTheme);
|
||||
}
|
||||
}
|
||||
|
@ -17,27 +17,27 @@ public class AsyncDrawable extends Drawable {
|
||||
void cancel(@NonNull String destination);
|
||||
}
|
||||
|
||||
private final String destination;
|
||||
private final Loader loader;
|
||||
private final String mDestination;
|
||||
private final Loader mLoader;
|
||||
|
||||
private Drawable result;
|
||||
private Callback callback;
|
||||
private Drawable mResult;
|
||||
private Callback mCallback;
|
||||
|
||||
public AsyncDrawable(@NonNull String destination, @NonNull Loader loader) {
|
||||
this.destination = destination;
|
||||
this.loader = loader;
|
||||
mDestination = destination;
|
||||
mLoader = loader;
|
||||
}
|
||||
|
||||
public String getDestination() {
|
||||
return destination;
|
||||
return mDestination;
|
||||
}
|
||||
|
||||
public Drawable getResult() {
|
||||
return result;
|
||||
return mResult;
|
||||
}
|
||||
|
||||
public boolean hasResult() {
|
||||
return result != null;
|
||||
return mResult != null;
|
||||
}
|
||||
|
||||
public boolean isAttached() {
|
||||
@ -47,39 +47,39 @@ public class AsyncDrawable extends Drawable {
|
||||
// yeah
|
||||
public void setCallback2(@Nullable Callback callback) {
|
||||
|
||||
this.callback = callback;
|
||||
mCallback = callback;
|
||||
super.setCallback(callback);
|
||||
|
||||
// if not null -> means we are attached
|
||||
if (callback != null) {
|
||||
loader.load(destination, this);
|
||||
mLoader.load(mDestination, this);
|
||||
} else {
|
||||
if (result != null) {
|
||||
if (mResult != null) {
|
||||
|
||||
result.setCallback(null);
|
||||
mResult.setCallback(null);
|
||||
|
||||
// let's additionally stop if it Animatable
|
||||
if (result instanceof Animatable) {
|
||||
((Animatable) result).stop();
|
||||
if (mResult instanceof Animatable) {
|
||||
((Animatable) mResult).stop();
|
||||
}
|
||||
}
|
||||
loader.cancel(destination);
|
||||
mLoader.cancel(mDestination);
|
||||
}
|
||||
}
|
||||
|
||||
public void setResult(@NonNull Drawable result) {
|
||||
|
||||
// if we have previous one, detach it
|
||||
if (this.result != null) {
|
||||
this.result.setCallback(null);
|
||||
if (mResult != null) {
|
||||
mResult.setCallback(null);
|
||||
}
|
||||
|
||||
this.result = result;
|
||||
this.result.setCallback(callback);
|
||||
mResult = result;
|
||||
mResult.setCallback(mCallback);
|
||||
|
||||
// should we copy the data here? like bounds etc?
|
||||
// if we are async and we load some image from some source
|
||||
// thr bounds might change... so we are better off copy `result` bounds to this instance
|
||||
// thr bounds might change... so we are better off copy `mResult` bounds to this instance
|
||||
setBounds(result.getBounds());
|
||||
invalidateSelf();
|
||||
}
|
||||
@ -87,7 +87,7 @@ public class AsyncDrawable extends Drawable {
|
||||
@Override
|
||||
public void draw(@NonNull Canvas canvas) {
|
||||
if (hasResult()) {
|
||||
result.draw(canvas);
|
||||
mResult.draw(canvas);
|
||||
}
|
||||
}
|
||||
|
||||
@ -105,7 +105,7 @@ public class AsyncDrawable extends Drawable {
|
||||
public int getOpacity() {
|
||||
final int opacity;
|
||||
if (hasResult()) {
|
||||
opacity = result.getOpacity();
|
||||
opacity = mResult.getOpacity();
|
||||
} else {
|
||||
opacity = PixelFormat.TRANSPARENT;
|
||||
}
|
||||
@ -116,7 +116,7 @@ public class AsyncDrawable extends Drawable {
|
||||
public int getIntrinsicWidth() {
|
||||
final int out;
|
||||
if (hasResult()) {
|
||||
out = result.getIntrinsicWidth();
|
||||
out = mResult.getIntrinsicWidth();
|
||||
} else {
|
||||
out = 0;
|
||||
}
|
||||
@ -127,7 +127,7 @@ public class AsyncDrawable extends Drawable {
|
||||
public int getIntrinsicHeight() {
|
||||
final int out;
|
||||
if (hasResult()) {
|
||||
out = result.getIntrinsicHeight();
|
||||
out = mResult.getIntrinsicHeight();
|
||||
} else {
|
||||
out = 0;
|
||||
}
|
||||
|
@ -22,15 +22,15 @@ public class AsyncDrawableSpan extends ReplacementSpan {
|
||||
|
||||
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
|
||||
public static final int ALIGN_CENTER = 2; // will only center if drawable height is less than mText line height
|
||||
|
||||
private final SpannableTheme theme;
|
||||
private final AsyncDrawable drawable;
|
||||
private final int alignment;
|
||||
private final boolean replacementTextIsLink;
|
||||
private final SpannableTheme mTheme;
|
||||
private final AsyncDrawable mDrawable;
|
||||
private final int mAlignment;
|
||||
private final boolean mReplacementTextIsLink;
|
||||
|
||||
private int lastKnownDrawX;
|
||||
private int lastKnownDrawY;
|
||||
private int mLastKnownDrawX;
|
||||
private int mLastKnownDrawY;
|
||||
|
||||
public AsyncDrawableSpan(@NonNull SpannableTheme theme, @NonNull AsyncDrawable drawable) {
|
||||
this(theme, drawable, ALIGN_BOTTOM);
|
||||
@ -48,10 +48,10 @@ public class AsyncDrawableSpan extends ReplacementSpan {
|
||||
@NonNull AsyncDrawable drawable,
|
||||
@Alignment int alignment,
|
||||
boolean replacementTextIsLink) {
|
||||
this.theme = theme;
|
||||
this.drawable = drawable;
|
||||
this.alignment = alignment;
|
||||
this.replacementTextIsLink = replacementTextIsLink;
|
||||
mTheme = theme;
|
||||
mDrawable = drawable;
|
||||
mAlignment = alignment;
|
||||
mReplacementTextIsLink = replacementTextIsLink;
|
||||
|
||||
// additionally set intrinsic bounds if empty
|
||||
final Rect rect = drawable.getBounds();
|
||||
@ -68,13 +68,13 @@ public class AsyncDrawableSpan extends ReplacementSpan {
|
||||
@IntRange(from = 0) int end,
|
||||
@Nullable Paint.FontMetricsInt fm) {
|
||||
|
||||
// if we have no async drawable result - we will just render text
|
||||
// if we have no async drawable result - we will just render mText
|
||||
|
||||
final int size;
|
||||
|
||||
if (drawable.hasResult()) {
|
||||
if (mDrawable.hasResult()) {
|
||||
|
||||
final Rect rect = drawable.getBounds();
|
||||
final Rect rect = mDrawable.getBounds();
|
||||
|
||||
if (fm != null) {
|
||||
fm.ascent = -rect.bottom;
|
||||
@ -89,11 +89,11 @@ public class AsyncDrawableSpan extends ReplacementSpan {
|
||||
} else {
|
||||
|
||||
// we will apply style here in case if theme modifies textSize or style (affects metrics)
|
||||
if (replacementTextIsLink) {
|
||||
theme.applyLinkStyle(paint);
|
||||
if (mReplacementTextIsLink) {
|
||||
mTheme.applyLinkStyle(paint);
|
||||
}
|
||||
|
||||
// NB, no specific text handling (no new lines, etc)
|
||||
// NB, no specific mText handling (no new lines, etc)
|
||||
size = (int) (paint.measureText(text, start, end) + .5F);
|
||||
}
|
||||
|
||||
@ -112,10 +112,10 @@ public class AsyncDrawableSpan extends ReplacementSpan {
|
||||
int bottom,
|
||||
@NonNull Paint paint) {
|
||||
|
||||
this.lastKnownDrawX = (int) (x + .5F);
|
||||
this.lastKnownDrawY = y;
|
||||
mLastKnownDrawX = (int) (x + .5F);
|
||||
mLastKnownDrawY = y;
|
||||
|
||||
final AsyncDrawable drawable = this.drawable;
|
||||
final AsyncDrawable drawable = mDrawable;
|
||||
|
||||
if (drawable.hasResult()) {
|
||||
|
||||
@ -124,9 +124,9 @@ public class AsyncDrawableSpan extends ReplacementSpan {
|
||||
final int save = canvas.save();
|
||||
try {
|
||||
final int translationY;
|
||||
if (ALIGN_CENTER == alignment) {
|
||||
if (ALIGN_CENTER == mAlignment) {
|
||||
translationY = b - ((bottom - top - drawable.getBounds().height()) / 2);
|
||||
} else if (ALIGN_BASELINE == alignment) {
|
||||
} else if (ALIGN_BASELINE == mAlignment) {
|
||||
translationY = b - paint.getFontMetricsInt().descent;
|
||||
} else {
|
||||
translationY = b;
|
||||
@ -142,24 +142,24 @@ public class AsyncDrawableSpan extends ReplacementSpan {
|
||||
// let's focus on main functionality and then think of it
|
||||
|
||||
final float textY = CanvasUtils.textCenterY(top, bottom, paint);
|
||||
if (replacementTextIsLink) {
|
||||
theme.applyLinkStyle(paint);
|
||||
if (mReplacementTextIsLink) {
|
||||
mTheme.applyLinkStyle(paint);
|
||||
}
|
||||
|
||||
// NB, no specific text handling (no new lines, etc)
|
||||
// NB, no specific mText handling (no new lines, etc)
|
||||
canvas.drawText(text, start, end, x, textY, paint);
|
||||
}
|
||||
}
|
||||
|
||||
public AsyncDrawable getDrawable() {
|
||||
return drawable;
|
||||
return mDrawable;
|
||||
}
|
||||
|
||||
public int lastKnownDrawX() {
|
||||
return lastKnownDrawX;
|
||||
return mLastKnownDrawX;
|
||||
}
|
||||
|
||||
public int lastKnownDrawY() {
|
||||
return lastKnownDrawY;
|
||||
return mLastKnownDrawY;
|
||||
}
|
||||
}
|
||||
|
@ -9,19 +9,19 @@ 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;
|
||||
private final SpannableTheme mTheme;
|
||||
private final Rect mRect = ObjectsPool.rect();
|
||||
private final Paint mPaint = ObjectsPool.paint();
|
||||
private final int mIndent;
|
||||
|
||||
public BlockQuoteSpan(@NonNull SpannableTheme theme, int indent) {
|
||||
this.theme = theme;
|
||||
this.indent = indent;
|
||||
mTheme = theme;
|
||||
mIndent = indent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLeadingMargin(boolean first) {
|
||||
return theme.getBlockMargin();
|
||||
return mTheme.getBlockMargin();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -39,13 +39,13 @@ public class BlockQuoteSpan implements LeadingMarginSpan {
|
||||
boolean first,
|
||||
Layout layout) {
|
||||
|
||||
final int width = theme.getBlockQuoteWidth();
|
||||
final int width = mTheme.getBlockQuoteWidth();
|
||||
|
||||
theme.applyBlockQuoteStyle(paint);
|
||||
mTheme.applyBlockQuoteStyle(mPaint);
|
||||
|
||||
final int left = theme.getBlockMargin() * (indent - 1);
|
||||
rect.set(left, top, left + width, bottom);
|
||||
final int left = mTheme.getBlockMargin() * (mIndent - 1);
|
||||
mRect.set(left, top, left + width, bottom);
|
||||
|
||||
c.drawRect(rect, paint);
|
||||
c.drawRect(mRect, mPaint);
|
||||
}
|
||||
}
|
||||
|
@ -11,27 +11,27 @@ import android.text.style.LeadingMarginSpan;
|
||||
|
||||
public class BulletListItemSpan implements LeadingMarginSpan {
|
||||
|
||||
private SpannableTheme theme;
|
||||
private SpannableTheme mTheme;
|
||||
|
||||
private final Paint paint = ObjectsPool.paint();
|
||||
private final RectF circle = ObjectsPool.rectF();
|
||||
private final Rect rectangle = ObjectsPool.rect();
|
||||
private final Paint mPaint = ObjectsPool.paint();
|
||||
private final RectF mCircle = ObjectsPool.rectF();
|
||||
private final Rect mRectangle = ObjectsPool.rect();
|
||||
|
||||
private final int blockIndent;
|
||||
private final int level;
|
||||
private final int mBlockIndent;
|
||||
private final int mLevel;
|
||||
|
||||
public BulletListItemSpan(
|
||||
@NonNull SpannableTheme theme,
|
||||
@IntRange(from = 0) int blockIndent,
|
||||
@IntRange(from = 0) int level) {
|
||||
this.theme = theme;
|
||||
this.blockIndent = blockIndent;
|
||||
this.level = level;
|
||||
mTheme = theme;
|
||||
mBlockIndent = blockIndent;
|
||||
mLevel = level;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLeadingMargin(boolean first) {
|
||||
return theme.getBlockMargin();
|
||||
return mTheme.getBlockMargin();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -42,44 +42,44 @@ public class BulletListItemSpan implements LeadingMarginSpan {
|
||||
return;
|
||||
}
|
||||
|
||||
paint.set(p);
|
||||
mPaint.set(p);
|
||||
|
||||
theme.applyListItemStyle(paint);
|
||||
mTheme.applyListItemStyle(mPaint);
|
||||
|
||||
final int save = c.save();
|
||||
try {
|
||||
|
||||
final int width = theme.getBlockMargin();
|
||||
final int width = mTheme.getBlockMargin();
|
||||
final int height = bottom - top;
|
||||
|
||||
final int side = theme.getBulletWidth(bottom - top);
|
||||
final int side = mTheme.getBulletWidth(bottom - top);
|
||||
|
||||
final int marginLeft = (width - side) / 2;
|
||||
final int marginTop = (height - side) / 2;
|
||||
|
||||
final int l = (width * (blockIndent - 1)) + marginLeft;
|
||||
final int l = (width * (mBlockIndent - 1)) + marginLeft;
|
||||
final int t = top + marginTop;
|
||||
final int r = l + side;
|
||||
final int b = t + side;
|
||||
|
||||
if (level == 0
|
||||
|| level == 1) {
|
||||
if (mLevel == 0
|
||||
|| mLevel == 1) {
|
||||
|
||||
circle.set(l, t, r, b);
|
||||
mCircle.set(l, t, r, b);
|
||||
|
||||
final Paint.Style style = level == 0
|
||||
final Paint.Style style = mLevel == 0
|
||||
? Paint.Style.FILL
|
||||
: Paint.Style.STROKE;
|
||||
paint.setStyle(style);
|
||||
mPaint.setStyle(style);
|
||||
|
||||
c.drawOval(circle, paint);
|
||||
c.drawOval(mCircle, mPaint);
|
||||
} else {
|
||||
|
||||
rectangle.set(l, t, r, b);
|
||||
mRectangle.set(l, t, r, b);
|
||||
|
||||
paint.setStyle(Paint.Style.FILL);
|
||||
mPaint.setStyle(Paint.Style.FILL);
|
||||
|
||||
c.drawRect(rectangle, paint);
|
||||
c.drawRect(mRectangle, mPaint);
|
||||
}
|
||||
|
||||
} finally {
|
||||
|
@ -11,15 +11,15 @@ 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 SpannableTheme mTheme;
|
||||
private final Rect mRect = ObjectsPool.rect();
|
||||
private final Paint mPaint = ObjectsPool.paint();
|
||||
|
||||
private final boolean multiline;
|
||||
private final boolean mMultiline;
|
||||
|
||||
public CodeSpan(@NonNull SpannableTheme theme, boolean multiline) {
|
||||
this.theme = theme;
|
||||
this.multiline = multiline;
|
||||
mTheme = theme;
|
||||
mMultiline = multiline;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -30,31 +30,31 @@ public class CodeSpan extends MetricAffectingSpan implements LeadingMarginSpan {
|
||||
@Override
|
||||
public void updateDrawState(TextPaint ds) {
|
||||
apply(ds);
|
||||
if (!multiline) {
|
||||
ds.bgColor = theme.getCodeBackgroundColor(ds);
|
||||
if (!mMultiline) {
|
||||
ds.bgColor = mTheme.getCodeBackgroundColor(ds);
|
||||
}
|
||||
}
|
||||
|
||||
private void apply(TextPaint p) {
|
||||
theme.applyCodeTextStyle(p);
|
||||
mTheme.applyCodeTextStyle(p);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLeadingMargin(boolean first) {
|
||||
return multiline ? theme.getCodeMultilineMargin() : 0;
|
||||
return mMultiline ? mTheme.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) {
|
||||
if (mMultiline) {
|
||||
|
||||
paint.setStyle(Paint.Style.FILL);
|
||||
paint.setColor(theme.getCodeBackgroundColor(p));
|
||||
mPaint.setStyle(Paint.Style.FILL);
|
||||
mPaint.setColor(mTheme.getCodeBackgroundColor(p));
|
||||
|
||||
rect.set(x, top, c.getWidth(), bottom);
|
||||
mRect.set(x, top, c.getWidth(), bottom);
|
||||
|
||||
c.drawRect(rect, paint);
|
||||
c.drawRect(mRect, mPaint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,16 +12,16 @@ 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 textLength;
|
||||
private final SpannableTheme mTheme;
|
||||
private final Rect mRect = ObjectsPool.rect();
|
||||
private final Paint mPaint = ObjectsPool.paint();
|
||||
private final int mLevel;
|
||||
private final int mTextLength;
|
||||
|
||||
public HeadingSpan(@NonNull SpannableTheme theme, @IntRange(from = 1, to = 6) int level, @IntRange(from = 0) int textLength) {
|
||||
this.theme = theme;
|
||||
this.level = level;
|
||||
this.textLength = textLength;
|
||||
mTheme = theme;
|
||||
mLevel = level;
|
||||
mTextLength = textLength;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -35,7 +35,7 @@ public class HeadingSpan extends MetricAffectingSpan implements LeadingMarginSpa
|
||||
}
|
||||
|
||||
private void apply(TextPaint paint) {
|
||||
theme.applyHeadingTextStyle(paint, level);
|
||||
mTheme.applyHeadingTextStyle(paint, mLevel);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -47,19 +47,19 @@ public class HeadingSpan extends MetricAffectingSpan implements LeadingMarginSpa
|
||||
@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 (mLevel == 1
|
||||
|| mLevel == 2) {
|
||||
|
||||
if ((start + textLength) == end) {
|
||||
paint.set(p);
|
||||
if ((start + mTextLength) == end) {
|
||||
mPaint.set(p);
|
||||
|
||||
theme.applyHeadingBreakStyle(paint);
|
||||
mTheme.applyHeadingBreakStyle(mPaint);
|
||||
|
||||
final float height = paint.getStrokeWidth();
|
||||
final float height = mPaint.getStrokeWidth();
|
||||
final int b = (int) (bottom - height + .5F);
|
||||
|
||||
rect.set(x, b, c.getWidth(), bottom);
|
||||
c.drawRect(rect, paint);
|
||||
mRect.set(x, b, c.getWidth(), bottom);
|
||||
c.drawRect(mRect, mPaint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,23 +11,23 @@ public class LinkSpan extends ClickableSpan {
|
||||
void resolve(View view, @NonNull String link);
|
||||
}
|
||||
|
||||
private final SpannableTheme theme;
|
||||
private final String link;
|
||||
private final Resolver resolver;
|
||||
private final SpannableTheme mTheme;
|
||||
private final String mLink;
|
||||
private final Resolver mResolver;
|
||||
|
||||
public LinkSpan(@NonNull SpannableTheme theme, @NonNull String link, @NonNull Resolver resolver) {
|
||||
this.theme = theme;
|
||||
this.link = link;
|
||||
this.resolver = resolver;
|
||||
mTheme = theme;
|
||||
mLink = link;
|
||||
mResolver = resolver;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View widget) {
|
||||
resolver.resolve(widget, link);
|
||||
mResolver.resolve(widget, mLink);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDrawState(TextPaint ds) {
|
||||
theme.applyLinkStyle(ds);
|
||||
mTheme.applyLinkStyle(ds);
|
||||
}
|
||||
}
|
||||
|
@ -9,23 +9,23 @@ import android.text.style.LeadingMarginSpan;
|
||||
|
||||
public class OrderedListItemSpan implements LeadingMarginSpan {
|
||||
|
||||
private final SpannableTheme theme;
|
||||
private final String number;
|
||||
private final int blockIndent;
|
||||
private final SpannableTheme mTheme;
|
||||
private final String mNumber;
|
||||
private final int mBlockIndent;
|
||||
|
||||
public OrderedListItemSpan(
|
||||
@NonNull SpannableTheme theme,
|
||||
@NonNull String number,
|
||||
@IntRange(from = 0) int blockIndent
|
||||
) {
|
||||
this.theme = theme;
|
||||
this.number = number;
|
||||
this.blockIndent = blockIndent;
|
||||
mTheme = theme;
|
||||
mNumber = number;
|
||||
mBlockIndent = blockIndent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLeadingMargin(boolean first) {
|
||||
return theme.getBlockMargin();
|
||||
return mTheme.getBlockMargin();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -36,14 +36,14 @@ public class OrderedListItemSpan implements LeadingMarginSpan {
|
||||
return;
|
||||
}
|
||||
|
||||
theme.applyListItemStyle(p);
|
||||
mTheme.applyListItemStyle(p);
|
||||
|
||||
final int width = theme.getBlockMargin();
|
||||
final int numberWidth = (int) (p.measureText(number) + .5F);
|
||||
final int numberX = (width * blockIndent) - numberWidth;
|
||||
final int width = mTheme.getBlockMargin();
|
||||
final int numberWidth = (int) (p.measureText(mNumber) + .5F);
|
||||
final int numberX = (width * mBlockIndent) - numberWidth;
|
||||
|
||||
final float numberY = CanvasUtils.textCenterY(top, bottom, p);
|
||||
|
||||
c.drawText(number, numberX, numberY, p);
|
||||
c.drawText(mNumber, numberX, numberY, p);
|
||||
}
|
||||
}
|
||||
|
@ -122,128 +122,128 @@ public class SpannableTheme {
|
||||
|
||||
protected static final int TABLE_ODD_ROW_DEF_ALPHA = 22;
|
||||
|
||||
protected final int linkColor;
|
||||
protected final int mLinkColor;
|
||||
|
||||
// used in quote, lists
|
||||
protected final int blockMargin;
|
||||
protected final int mBlockMargin;
|
||||
|
||||
// by default it's 1/4th of `blockMargin`
|
||||
protected final int blockQuoteWidth;
|
||||
// by default it's 1/4th of `mBlockMargin`
|
||||
protected final int mBlockQuoteWidth;
|
||||
|
||||
// by default it's text color with `BLOCK_QUOTE_DEF_COLOR_ALPHA` applied alpha
|
||||
protected final int blockQuoteColor;
|
||||
// by default it's mText color with `BLOCK_QUOTE_DEF_COLOR_ALPHA` applied alpha
|
||||
protected final int mBlockQuoteColor;
|
||||
|
||||
// by default uses text color (applied for un-ordered lists & ordered (bullets & numbers)
|
||||
protected final int listItemColor;
|
||||
// by default uses mText color (applied for un-ordered lists & ordered (bullets & numbers)
|
||||
protected final int mListItemColor;
|
||||
|
||||
// by default the stroke color of a paint object
|
||||
protected final int bulletListItemStrokeWidth;
|
||||
protected final int mBulletListItemStrokeWidth;
|
||||
|
||||
// width of bullet, by default min(blockMargin, height) / 2
|
||||
protected final int bulletWidth;
|
||||
// width of bullet, by default min(mBlockMargin, height) / 2
|
||||
protected final int mBulletWidth;
|
||||
|
||||
// by default - main text color
|
||||
protected final int codeTextColor;
|
||||
// by default - main mText color
|
||||
protected final int mCodeTextColor;
|
||||
|
||||
// by default 0.1 alpha of textColor/codeTextColor
|
||||
protected final int codeBackgroundColor;
|
||||
// by default 0.1 alpha of textColor/mCodeTextColor
|
||||
protected final int mCodeBackgroundColor;
|
||||
|
||||
// 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;
|
||||
protected final int mCodeMultilineMargin;
|
||||
|
||||
// by default Typeface.MONOSPACE
|
||||
protected final Typeface codeTypeface;
|
||||
protected final Typeface mCodeTypeface;
|
||||
|
||||
// by default a bit (how much?!) smaller than normal text
|
||||
// by default a bit (how much?!) smaller than normal mText
|
||||
// applied ONLY if default typeface was used, otherwise, not applied
|
||||
protected final int codeTextSize;
|
||||
protected final int mCodeTextSize;
|
||||
|
||||
// by default paint.getStrokeWidth
|
||||
protected final int headingBreakHeight;
|
||||
protected final int mHeadingBreakHeight;
|
||||
|
||||
// by default, text color with `HEADING_DEF_BREAK_COLOR_ALPHA` applied alpha
|
||||
protected final int headingBreakColor;
|
||||
// by default, mText color with `HEADING_DEF_BREAK_COLOR_ALPHA` applied alpha
|
||||
protected final int mHeadingBreakColor;
|
||||
|
||||
// by default `SCRIPT_DEF_TEXT_SIZE_RATIO`
|
||||
protected final float scriptTextSizeRatio;
|
||||
protected final float mScriptTextSizeRatio;
|
||||
|
||||
// by default textColor with `THEMATIC_BREAK_DEF_ALPHA` applied alpha
|
||||
protected final int thematicBreakColor;
|
||||
protected final int mThematicBreakColor;
|
||||
|
||||
// by default paint.strokeWidth
|
||||
protected final int thematicBreakHeight;
|
||||
protected final int mThematicBreakHeight;
|
||||
|
||||
// by default 0
|
||||
protected final int tableCellPadding;
|
||||
protected final int mTableCellPadding;
|
||||
|
||||
// by default paint.color * TABLE_BORDER_DEF_ALPHA
|
||||
protected final int tableBorderColor;
|
||||
protected final int mTableBorderColor;
|
||||
|
||||
protected final int tableBorderWidth;
|
||||
protected final int mTableBorderWidth;
|
||||
|
||||
// by default paint.color * TABLE_ODD_ROW_DEF_ALPHA
|
||||
protected final int tableOddRowBackgroundColor;
|
||||
protected final int mTableOddRowBackgroundColor;
|
||||
|
||||
// drawable that will be used to render checkbox (should be stateful)
|
||||
// TaskListDrawable can be used
|
||||
protected final Drawable taskListDrawable;
|
||||
protected final Drawable mTaskListDrawable;
|
||||
|
||||
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;
|
||||
this.tableCellPadding = builder.tableCellPadding;
|
||||
this.tableBorderColor = builder.tableBorderColor;
|
||||
this.tableBorderWidth = builder.tableBorderWidth;
|
||||
this.tableOddRowBackgroundColor = builder.tableOddRowBackgroundColor;
|
||||
this.taskListDrawable = builder.taskListDrawable;
|
||||
mLinkColor = builder.linkColor;
|
||||
mBlockMargin = builder.blockMargin;
|
||||
mBlockQuoteWidth = builder.blockQuoteWidth;
|
||||
mBlockQuoteColor = builder.blockQuoteColor;
|
||||
mListItemColor = builder.listItemColor;
|
||||
mBulletListItemStrokeWidth = builder.bulletListItemStrokeWidth;
|
||||
mBulletWidth = builder.bulletWidth;
|
||||
mCodeTextColor = builder.codeTextColor;
|
||||
mCodeBackgroundColor = builder.codeBackgroundColor;
|
||||
mCodeMultilineMargin = builder.codeMultilineMargin;
|
||||
mCodeTypeface = builder.codeTypeface;
|
||||
mCodeTextSize = builder.codeTextSize;
|
||||
mHeadingBreakHeight = builder.headingBreakHeight;
|
||||
mHeadingBreakColor = builder.headingBreakColor;
|
||||
mScriptTextSizeRatio = builder.scriptTextSizeRatio;
|
||||
mThematicBreakColor = builder.thematicBreakColor;
|
||||
mThematicBreakHeight = builder.thematicBreakHeight;
|
||||
mTableCellPadding = builder.tableCellPadding;
|
||||
mTableBorderColor = builder.tableBorderColor;
|
||||
mTableBorderWidth = builder.tableBorderWidth;
|
||||
mTableOddRowBackgroundColor = builder.tableOddRowBackgroundColor;
|
||||
mTaskListDrawable = builder.taskListDrawable;
|
||||
}
|
||||
|
||||
|
||||
public void applyLinkStyle(@NonNull Paint paint) {
|
||||
paint.setUnderlineText(true);
|
||||
if (linkColor != 0) {
|
||||
// by default we will be using text color
|
||||
paint.setColor(linkColor);
|
||||
if (mLinkColor != 0) {
|
||||
// by default we will be using mText color
|
||||
paint.setColor(mLinkColor);
|
||||
}
|
||||
}
|
||||
|
||||
public void applyBlockQuoteStyle(@NonNull Paint paint) {
|
||||
final int color;
|
||||
if (blockQuoteColor == 0) {
|
||||
if (mBlockQuoteColor == 0) {
|
||||
color = ColorUtils.applyAlpha(paint.getColor(), BLOCK_QUOTE_DEF_COLOR_ALPHA);
|
||||
} else {
|
||||
color = blockQuoteColor;
|
||||
color = mBlockQuoteColor;
|
||||
}
|
||||
paint.setStyle(Paint.Style.FILL);
|
||||
paint.setColor(color);
|
||||
}
|
||||
|
||||
public int getBlockMargin() {
|
||||
return blockMargin;
|
||||
return mBlockMargin;
|
||||
}
|
||||
|
||||
public int getBlockQuoteWidth() {
|
||||
final int out;
|
||||
if (blockQuoteWidth == 0) {
|
||||
out = (int) (blockMargin * .25F + .5F);
|
||||
if (mBlockQuoteWidth == 0) {
|
||||
out = (int) (mBlockMargin * .25F + .5F);
|
||||
} else {
|
||||
out = blockQuoteWidth;
|
||||
out = mBlockQuoteWidth;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
@ -251,28 +251,28 @@ public class SpannableTheme {
|
||||
public void applyListItemStyle(@NonNull Paint paint) {
|
||||
|
||||
final int color;
|
||||
if (listItemColor != 0) {
|
||||
color = listItemColor;
|
||||
if (mListItemColor != 0) {
|
||||
color = mListItemColor;
|
||||
} else {
|
||||
color = paint.getColor();
|
||||
}
|
||||
paint.setColor(color);
|
||||
|
||||
if (bulletListItemStrokeWidth != 0) {
|
||||
paint.setStrokeWidth(bulletListItemStrokeWidth);
|
||||
if (mBulletListItemStrokeWidth != 0) {
|
||||
paint.setStrokeWidth(mBulletListItemStrokeWidth);
|
||||
}
|
||||
}
|
||||
|
||||
public int getBulletWidth(int height) {
|
||||
|
||||
final int min = Math.min(blockMargin, height) / 2;
|
||||
final int min = Math.min(mBlockMargin, height) / 2;
|
||||
|
||||
final int width;
|
||||
if (bulletWidth == 0
|
||||
|| bulletWidth > min) {
|
||||
if (mBulletWidth == 0
|
||||
|| mBulletWidth > min) {
|
||||
width = min;
|
||||
} else {
|
||||
width = bulletWidth;
|
||||
width = mBulletWidth;
|
||||
}
|
||||
|
||||
return width;
|
||||
@ -280,27 +280,27 @@ public class SpannableTheme {
|
||||
|
||||
public void applyCodeTextStyle(@NonNull Paint paint) {
|
||||
|
||||
if (codeTextColor != 0) {
|
||||
paint.setColor(codeTextColor);
|
||||
if (mCodeTextColor != 0) {
|
||||
paint.setColor(mCodeTextColor);
|
||||
}
|
||||
|
||||
// custom typeface was set
|
||||
if (codeTypeface != null) {
|
||||
if (mCodeTypeface != null) {
|
||||
|
||||
paint.setTypeface(codeTypeface);
|
||||
paint.setTypeface(mCodeTypeface);
|
||||
|
||||
// please note that we won't be calculating textSize
|
||||
// (like we do when no Typeface is provided), if it's some specific typeface
|
||||
// we would confuse users about textSize
|
||||
if (codeTextSize != 0) {
|
||||
paint.setTextSize(codeTextSize);
|
||||
if (mCodeTextSize != 0) {
|
||||
paint.setTextSize(mCodeTextSize);
|
||||
}
|
||||
|
||||
} else {
|
||||
paint.setTypeface(Typeface.MONOSPACE);
|
||||
final float textSize;
|
||||
if (codeTextSize != 0) {
|
||||
textSize = codeTextSize;
|
||||
if (mCodeTextSize != 0) {
|
||||
textSize = mCodeTextSize;
|
||||
} else {
|
||||
textSize = paint.getTextSize() * CODE_DEF_TEXT_SIZE_RATIO;
|
||||
}
|
||||
@ -309,13 +309,13 @@ public class SpannableTheme {
|
||||
}
|
||||
|
||||
public int getCodeMultilineMargin() {
|
||||
return codeMultilineMargin;
|
||||
return mCodeMultilineMargin;
|
||||
}
|
||||
|
||||
public int getCodeBackgroundColor(@NonNull Paint paint) {
|
||||
final int color;
|
||||
if (codeBackgroundColor != 0) {
|
||||
color = codeBackgroundColor;
|
||||
if (mCodeBackgroundColor != 0) {
|
||||
color = mCodeBackgroundColor;
|
||||
} else {
|
||||
color = ColorUtils.applyAlpha(paint.getColor(), CODE_DEF_BACKGROUND_COLOR_ALPHA);
|
||||
}
|
||||
@ -329,25 +329,25 @@ public class SpannableTheme {
|
||||
|
||||
public void applyHeadingBreakStyle(@NonNull Paint paint) {
|
||||
final int color;
|
||||
if (headingBreakColor != 0) {
|
||||
color = headingBreakColor;
|
||||
if (mHeadingBreakColor != 0) {
|
||||
color = mHeadingBreakColor;
|
||||
} else {
|
||||
color = ColorUtils.applyAlpha(paint.getColor(), HEADING_DEF_BREAK_COLOR_ALPHA);
|
||||
}
|
||||
paint.setColor(color);
|
||||
paint.setStyle(Paint.Style.FILL);
|
||||
if (headingBreakHeight != 0) {
|
||||
if (mHeadingBreakHeight != 0) {
|
||||
//noinspection SuspiciousNameCombination
|
||||
paint.setStrokeWidth(headingBreakHeight);
|
||||
paint.setStrokeWidth(mHeadingBreakHeight);
|
||||
}
|
||||
}
|
||||
|
||||
public void applySuperScriptStyle(@NonNull TextPaint paint) {
|
||||
final float ratio;
|
||||
if (Float.compare(scriptTextSizeRatio, .0F) == 0) {
|
||||
if (Float.compare(mScriptTextSizeRatio, .0F) == 0) {
|
||||
ratio = SCRIPT_DEF_TEXT_SIZE_RATIO;
|
||||
} else {
|
||||
ratio = scriptTextSizeRatio;
|
||||
ratio = mScriptTextSizeRatio;
|
||||
}
|
||||
paint.setTextSize(paint.getTextSize() * ratio);
|
||||
paint.baselineShift += (int) (paint.ascent() / 2);
|
||||
@ -355,10 +355,10 @@ public class SpannableTheme {
|
||||
|
||||
public void applySubScriptStyle(@NonNull TextPaint paint) {
|
||||
final float ratio;
|
||||
if (Float.compare(scriptTextSizeRatio, .0F) == 0) {
|
||||
if (Float.compare(mScriptTextSizeRatio, .0F) == 0) {
|
||||
ratio = SCRIPT_DEF_TEXT_SIZE_RATIO;
|
||||
} else {
|
||||
ratio = scriptTextSizeRatio;
|
||||
ratio = mScriptTextSizeRatio;
|
||||
}
|
||||
paint.setTextSize(paint.getTextSize() * ratio);
|
||||
paint.baselineShift -= (int) (paint.ascent() / 2);
|
||||
@ -366,35 +366,35 @@ public class SpannableTheme {
|
||||
|
||||
public void applyThematicBreakStyle(@NonNull Paint paint) {
|
||||
final int color;
|
||||
if (thematicBreakColor != 0) {
|
||||
color = thematicBreakColor;
|
||||
if (mThematicBreakColor != 0) {
|
||||
color = mThematicBreakColor;
|
||||
} else {
|
||||
color = ColorUtils.applyAlpha(paint.getColor(), THEMATIC_BREAK_DEF_ALPHA);
|
||||
}
|
||||
paint.setColor(color);
|
||||
paint.setStyle(Paint.Style.FILL);
|
||||
|
||||
if (thematicBreakHeight != 0) {
|
||||
if (mThematicBreakHeight != 0) {
|
||||
//noinspection SuspiciousNameCombination
|
||||
paint.setStrokeWidth(thematicBreakHeight);
|
||||
paint.setStrokeWidth(mThematicBreakHeight);
|
||||
}
|
||||
}
|
||||
|
||||
public int tableCellPadding() {
|
||||
return tableCellPadding;
|
||||
return mTableCellPadding;
|
||||
}
|
||||
|
||||
public void applyTableBorderStyle(@NonNull Paint paint) {
|
||||
|
||||
final int color;
|
||||
if (tableBorderColor == 0) {
|
||||
if (mTableBorderColor == 0) {
|
||||
color = ColorUtils.applyAlpha(paint.getColor(), TABLE_BORDER_DEF_ALPHA);
|
||||
} else {
|
||||
color = tableBorderColor;
|
||||
color = mTableBorderColor;
|
||||
}
|
||||
|
||||
if (tableBorderWidth != 0) {
|
||||
paint.setStrokeWidth(tableBorderWidth);
|
||||
if (mTableBorderWidth != 0) {
|
||||
paint.setStrokeWidth(mTableBorderWidth);
|
||||
}
|
||||
|
||||
paint.setColor(color);
|
||||
@ -403,10 +403,10 @@ public class SpannableTheme {
|
||||
|
||||
public void applyTableOddRowStyle(@NonNull Paint paint) {
|
||||
final int color;
|
||||
if (tableOddRowBackgroundColor == 0) {
|
||||
if (mTableOddRowBackgroundColor == 0) {
|
||||
color = ColorUtils.applyAlpha(paint.getColor(), TABLE_ODD_ROW_DEF_ALPHA);
|
||||
} else {
|
||||
color = tableOddRowBackgroundColor;
|
||||
color = mTableOddRowBackgroundColor;
|
||||
}
|
||||
paint.setColor(color);
|
||||
paint.setStyle(Paint.Style.FILL);
|
||||
@ -418,7 +418,7 @@ public class SpannableTheme {
|
||||
*/
|
||||
@Nullable
|
||||
public Drawable getTaskListDrawable() {
|
||||
return taskListDrawable;
|
||||
return mTaskListDrawable;
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
@ -450,28 +450,28 @@ public class SpannableTheme {
|
||||
}
|
||||
|
||||
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;
|
||||
this.tableCellPadding = theme.tableCellPadding;
|
||||
this.tableBorderColor = theme.tableBorderColor;
|
||||
this.tableBorderWidth = theme.tableBorderWidth;
|
||||
this.tableOddRowBackgroundColor = theme.tableOddRowBackgroundColor;
|
||||
this.taskListDrawable = theme.taskListDrawable;
|
||||
linkColor = theme.mLinkColor;
|
||||
blockMargin = theme.mBlockMargin;
|
||||
blockQuoteWidth = theme.mBlockQuoteWidth;
|
||||
blockQuoteColor = theme.mBlockQuoteColor;
|
||||
listItemColor = theme.mListItemColor;
|
||||
bulletListItemStrokeWidth = theme.mBulletListItemStrokeWidth;
|
||||
bulletWidth = theme.mBulletWidth;
|
||||
codeTextColor = theme.mCodeTextColor;
|
||||
codeBackgroundColor = theme.mCodeBackgroundColor;
|
||||
codeMultilineMargin = theme.mCodeMultilineMargin;
|
||||
codeTypeface = theme.mCodeTypeface;
|
||||
codeTextSize = theme.mCodeTextSize;
|
||||
headingBreakHeight = theme.mHeadingBreakHeight;
|
||||
headingBreakColor = theme.mHeadingBreakColor;
|
||||
scriptTextSizeRatio = theme.mScriptTextSizeRatio;
|
||||
thematicBreakColor = theme.mThematicBreakColor;
|
||||
thematicBreakHeight = theme.mThematicBreakHeight;
|
||||
tableCellPadding = theme.mTableCellPadding;
|
||||
tableBorderColor = theme.mTableBorderColor;
|
||||
tableBorderWidth = theme.mTableBorderWidth;
|
||||
tableOddRowBackgroundColor = theme.mTableOddRowBackgroundColor;
|
||||
taskListDrawable = theme.mTaskListDrawable;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@ -624,14 +624,14 @@ public class SpannableTheme {
|
||||
|
||||
private static class Dip {
|
||||
|
||||
private final float density;
|
||||
private final float mDensity;
|
||||
|
||||
Dip(@NonNull Context context) {
|
||||
this.density = context.getResources().getDisplayMetrics().density;
|
||||
mDensity = context.getResources().getDisplayMetrics().density;
|
||||
}
|
||||
|
||||
int toPx(int dp) {
|
||||
return (int) (dp * density + .5F);
|
||||
return (int) (dp * mDensity + .5F);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,10 +6,10 @@ import android.text.style.MetricAffectingSpan;
|
||||
|
||||
public class SubScriptSpan extends MetricAffectingSpan {
|
||||
|
||||
private final SpannableTheme theme;
|
||||
private final SpannableTheme mTheme;
|
||||
|
||||
public SubScriptSpan(@NonNull SpannableTheme theme) {
|
||||
this.theme = theme;
|
||||
mTheme = theme;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -23,6 +23,6 @@ public class SubScriptSpan extends MetricAffectingSpan {
|
||||
}
|
||||
|
||||
private void apply(TextPaint paint) {
|
||||
theme.applySubScriptStyle(paint);
|
||||
mTheme.applySubScriptStyle(paint);
|
||||
}
|
||||
}
|
||||
|
@ -6,10 +6,10 @@ import android.text.style.MetricAffectingSpan;
|
||||
|
||||
public class SuperScriptSpan extends MetricAffectingSpan {
|
||||
|
||||
private final SpannableTheme theme;
|
||||
private final SpannableTheme mTheme;
|
||||
|
||||
public SuperScriptSpan(@NonNull SpannableTheme theme) {
|
||||
this.theme = theme;
|
||||
mTheme = theme;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -23,6 +23,6 @@ public class SuperScriptSpan extends MetricAffectingSpan {
|
||||
}
|
||||
|
||||
private void apply(TextPaint paint) {
|
||||
theme.applySuperScriptStyle(paint);
|
||||
mTheme.applySuperScriptStyle(paint);
|
||||
}
|
||||
}
|
||||
|
@ -35,57 +35,57 @@ public class TableRowSpan extends ReplacementSpan {
|
||||
|
||||
public static class Cell {
|
||||
|
||||
final int alignment;
|
||||
final CharSequence text;
|
||||
final int mAlignment;
|
||||
final CharSequence mText;
|
||||
|
||||
public Cell(@Alignment int alignment, CharSequence text) {
|
||||
this.alignment = alignment;
|
||||
this.text = text;
|
||||
mAlignment = alignment;
|
||||
mText = text;
|
||||
}
|
||||
|
||||
@Alignment
|
||||
public int alignment() {
|
||||
return alignment;
|
||||
return mAlignment;
|
||||
}
|
||||
|
||||
public CharSequence text() {
|
||||
return text;
|
||||
return mText;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Cell{" +
|
||||
"alignment=" + alignment +
|
||||
", text=" + text +
|
||||
"mAlignment=" + mAlignment +
|
||||
", mText=" + mText +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
private final SpannableTheme theme;
|
||||
private final List<Cell> cells;
|
||||
private final List<StaticLayout> layouts;
|
||||
private final TextPaint textPaint;
|
||||
private final boolean header;
|
||||
private final boolean odd;
|
||||
private final SpannableTheme mTheme;
|
||||
private final List<Cell> mCells;
|
||||
private final List<StaticLayout> mLayouts;
|
||||
private final TextPaint mTextPaint;
|
||||
private final boolean mHeader;
|
||||
private final boolean mOdd;
|
||||
|
||||
private final Rect rect = ObjectsPool.rect();
|
||||
private final Paint paint = ObjectsPool.paint();
|
||||
private final Rect mRect = ObjectsPool.rect();
|
||||
private final Paint mPaint = ObjectsPool.paint();
|
||||
|
||||
private int width;
|
||||
private int height;
|
||||
private Invalidator invalidator;
|
||||
private int mWidth;
|
||||
private int mHeight;
|
||||
private Invalidator mInvalidator;
|
||||
|
||||
public TableRowSpan(
|
||||
@NonNull SpannableTheme theme,
|
||||
@NonNull List<Cell> cells,
|
||||
boolean header,
|
||||
boolean odd) {
|
||||
this.theme = theme;
|
||||
this.cells = cells;
|
||||
this.layouts = new ArrayList<>(cells.size());
|
||||
this.textPaint = new TextPaint();
|
||||
this.header = header;
|
||||
this.odd = odd;
|
||||
mTheme = theme;
|
||||
mCells = cells;
|
||||
mLayouts = new ArrayList<>(cells.size());
|
||||
mTextPaint = new TextPaint();
|
||||
mHeader = header;
|
||||
mOdd = odd;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -97,14 +97,14 @@ public class TableRowSpan extends ReplacementSpan {
|
||||
@Nullable Paint.FontMetricsInt fm) {
|
||||
|
||||
// it's our absolute requirement to have width of the canvas here... because, well, it changes
|
||||
// the way we draw text. So, if we do not know the width of canvas we cannot correctly measure our text
|
||||
// the way we draw mText. So, if we do not know the width of canvas we cannot correctly measure our mText
|
||||
|
||||
if (layouts.size() > 0) {
|
||||
if (mLayouts.size() > 0) {
|
||||
|
||||
if (fm != null) {
|
||||
|
||||
int max = 0;
|
||||
for (StaticLayout layout : layouts) {
|
||||
for (StaticLayout layout : mLayouts) {
|
||||
final int height = layout.getHeight();
|
||||
if (height > max) {
|
||||
max = height;
|
||||
@ -112,10 +112,10 @@ public class TableRowSpan extends ReplacementSpan {
|
||||
}
|
||||
|
||||
// we store actual height
|
||||
height = max;
|
||||
mHeight = max;
|
||||
|
||||
// but apply height with padding
|
||||
final int padding = theme.tableCellPadding() * 2;
|
||||
final int padding = mTheme.tableCellPadding() * 2;
|
||||
|
||||
fm.ascent = -(max + padding);
|
||||
fm.descent = 0;
|
||||
@ -125,7 +125,7 @@ public class TableRowSpan extends ReplacementSpan {
|
||||
}
|
||||
}
|
||||
|
||||
return width;
|
||||
return mWidth;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -141,46 +141,46 @@ public class TableRowSpan extends ReplacementSpan {
|
||||
@NonNull Paint paint) {
|
||||
|
||||
if (recreateLayouts(canvas.getWidth())) {
|
||||
width = canvas.getWidth();
|
||||
textPaint.set(paint);
|
||||
mWidth = canvas.getWidth();
|
||||
mTextPaint.set(paint);
|
||||
makeNewLayouts();
|
||||
}
|
||||
|
||||
int maxHeight = 0;
|
||||
|
||||
final int padding = theme.tableCellPadding();
|
||||
final int padding = mTheme.tableCellPadding();
|
||||
|
||||
final int size = layouts.size();
|
||||
final int size = mLayouts.size();
|
||||
|
||||
final int w = width / size;
|
||||
final int w = mWidth / size;
|
||||
|
||||
// feels like magic...
|
||||
final int heightDiff = (bottom - top - height) / 4;
|
||||
final int heightDiff = (bottom - top - mHeight) / 4;
|
||||
|
||||
if (odd) {
|
||||
if (mOdd) {
|
||||
final int save = canvas.save();
|
||||
try {
|
||||
rect.set(0, 0, width, bottom - top);
|
||||
theme.applyTableOddRowStyle(this.paint);
|
||||
mRect.set(0, 0, mWidth, bottom - top);
|
||||
mTheme.applyTableOddRowStyle(mPaint);
|
||||
canvas.translate(x, top - heightDiff);
|
||||
canvas.drawRect(rect, this.paint);
|
||||
canvas.drawRect(mRect, mPaint);
|
||||
} finally {
|
||||
canvas.restoreToCount(save);
|
||||
}
|
||||
}
|
||||
|
||||
rect.set(0, 0, w, bottom - top);
|
||||
mRect.set(0, 0, w, bottom - top);
|
||||
|
||||
theme.applyTableBorderStyle(this.paint);
|
||||
mTheme.applyTableBorderStyle(mPaint);
|
||||
|
||||
StaticLayout layout;
|
||||
for (int i = 0; i < size; i++) {
|
||||
layout = layouts.get(i);
|
||||
layout = mLayouts.get(i);
|
||||
final int save = canvas.save();
|
||||
try {
|
||||
|
||||
canvas.translate(x + (i * w), top - heightDiff);
|
||||
canvas.drawRect(rect, this.paint);
|
||||
canvas.drawRect(mRect, mPaint);
|
||||
|
||||
canvas.translate(padding, padding + heightDiff);
|
||||
layout.draw(canvas);
|
||||
@ -194,40 +194,40 @@ public class TableRowSpan extends ReplacementSpan {
|
||||
}
|
||||
}
|
||||
|
||||
if (height != maxHeight) {
|
||||
if (invalidator != null) {
|
||||
invalidator.invalidate();
|
||||
if (mHeight != maxHeight) {
|
||||
if (mInvalidator != null) {
|
||||
mInvalidator.invalidate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean recreateLayouts(int newWidth) {
|
||||
return width != newWidth;
|
||||
return mWidth != newWidth;
|
||||
}
|
||||
|
||||
private void makeNewLayouts() {
|
||||
|
||||
textPaint.setFakeBoldText(header);
|
||||
mTextPaint.setFakeBoldText(mHeader);
|
||||
|
||||
final int columns = cells.size();
|
||||
final int padding = theme.tableCellPadding() * 2;
|
||||
final int w = (width / columns) - padding;
|
||||
final int columns = mCells.size();
|
||||
final int padding = mTheme.tableCellPadding() * 2;
|
||||
final int w = (mWidth / columns) - padding;
|
||||
|
||||
this.layouts.clear();
|
||||
mLayouts.clear();
|
||||
Cell cell;
|
||||
StaticLayout layout;
|
||||
for (int i = 0, size = cells.size(); i < size; i++) {
|
||||
cell = cells.get(i);
|
||||
for (int i = 0, size = mCells.size(); i < size; i++) {
|
||||
cell = mCells.get(i);
|
||||
layout = new StaticLayout(
|
||||
cell.text,
|
||||
textPaint,
|
||||
cell.mText,
|
||||
mTextPaint,
|
||||
w,
|
||||
alignment(cell.alignment),
|
||||
alignment(cell.mAlignment),
|
||||
1.F,
|
||||
.0F,
|
||||
false
|
||||
);
|
||||
layouts.add(layout);
|
||||
mLayouts.add(layout);
|
||||
}
|
||||
}
|
||||
|
||||
@ -249,7 +249,7 @@ public class TableRowSpan extends ReplacementSpan {
|
||||
}
|
||||
|
||||
public TableRowSpan invalidator(Invalidator invalidator) {
|
||||
this.invalidator = invalidator;
|
||||
mInvalidator = invalidator;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
@ -24,25 +24,25 @@ public class TaskListDrawable extends Drawable {
|
||||
private static final Point POINT_1 = new Point(7.F / 18, 12.5F / 18);
|
||||
private static final Point POINT_2 = new Point(15.25F / 18, 4.75F / 18);
|
||||
|
||||
private final int checkedFillColor;
|
||||
private final int normalOutlineColor;
|
||||
private final int mCheckedFillColor;
|
||||
private final int mNormalOutlineColor;
|
||||
|
||||
private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
private final RectF rectF = new RectF();
|
||||
private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
private final RectF mRectF = new RectF();
|
||||
|
||||
private final Paint checkMarkPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
private final Path checkMarkPath = new Path();
|
||||
private final Paint mCheckMarkPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
private final Path mCheckMarkPath = new Path();
|
||||
|
||||
private boolean isChecked;
|
||||
private boolean mIsChecked;
|
||||
|
||||
// unfortunately we cannot rely on TextView to be LAYER_TYPE_SOFTWARE
|
||||
// if we could we would draw our checkMarkPath with PorterDuff.CLEAR
|
||||
// if we could we would draw our mCheckMarkPath with PorterDuff.CLEAR
|
||||
public TaskListDrawable(@ColorInt int checkedFillColor, @ColorInt int normalOutlineColor, @ColorInt int checkMarkColor) {
|
||||
this.checkedFillColor = checkedFillColor;
|
||||
this.normalOutlineColor = normalOutlineColor;
|
||||
mCheckedFillColor = checkedFillColor;
|
||||
mNormalOutlineColor = normalOutlineColor;
|
||||
|
||||
checkMarkPaint.setColor(checkMarkColor);
|
||||
checkMarkPaint.setStyle(Paint.Style.STROKE);
|
||||
mCheckMarkPaint.setColor(checkMarkColor);
|
||||
mCheckMarkPaint.setStyle(Paint.Style.STROKE);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -56,16 +56,16 @@ public class TaskListDrawable extends Drawable {
|
||||
final float stroke = min / 8;
|
||||
|
||||
final float side = min - stroke;
|
||||
rectF.set(0, 0, side, side);
|
||||
mRectF.set(0, 0, side, side);
|
||||
|
||||
paint.setStrokeWidth(stroke);
|
||||
checkMarkPaint.setStrokeWidth(stroke);
|
||||
mPaint.setStrokeWidth(stroke);
|
||||
mCheckMarkPaint.setStrokeWidth(stroke);
|
||||
|
||||
checkMarkPath.reset();
|
||||
mCheckMarkPath.reset();
|
||||
|
||||
POINT_0.moveTo(checkMarkPath, side);
|
||||
POINT_1.lineTo(checkMarkPath, side);
|
||||
POINT_2.lineTo(checkMarkPath, side);
|
||||
POINT_0.moveTo(mCheckMarkPath, side);
|
||||
POINT_1.lineTo(mCheckMarkPath, side);
|
||||
POINT_2.lineTo(mCheckMarkPath, side);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -74,32 +74,32 @@ public class TaskListDrawable extends Drawable {
|
||||
final Paint.Style style;
|
||||
final int color;
|
||||
|
||||
if (isChecked) {
|
||||
if (mIsChecked) {
|
||||
style = Paint.Style.FILL_AND_STROKE;
|
||||
color = checkedFillColor;
|
||||
color = mCheckedFillColor;
|
||||
} else {
|
||||
style = Paint.Style.STROKE;
|
||||
color = normalOutlineColor;
|
||||
color = mNormalOutlineColor;
|
||||
}
|
||||
paint.setStyle(style);
|
||||
paint.setColor(color);
|
||||
mPaint.setStyle(style);
|
||||
mPaint.setColor(color);
|
||||
|
||||
final Rect bounds = getBounds();
|
||||
|
||||
final float left = (bounds.width() - rectF.width()) / 2;
|
||||
final float top = (bounds.height() - rectF.height()) / 2;
|
||||
final float left = (bounds.width() - mRectF.width()) / 2;
|
||||
final float top = (bounds.height() - mRectF.height()) / 2;
|
||||
|
||||
final float radius = rectF.width() / 8;
|
||||
final float radius = mRectF.width() / 8;
|
||||
|
||||
final int save = canvas.save();
|
||||
try {
|
||||
|
||||
canvas.translate(left, top);
|
||||
|
||||
canvas.drawRoundRect(rectF, radius, radius, paint);
|
||||
canvas.drawRoundRect(mRectF, radius, radius, mPaint);
|
||||
|
||||
if (isChecked) {
|
||||
canvas.drawPath(checkMarkPath, checkMarkPaint);
|
||||
if (mIsChecked) {
|
||||
canvas.drawPath(mCheckMarkPath, mCheckMarkPaint);
|
||||
}
|
||||
} finally {
|
||||
canvas.restoreToCount(save);
|
||||
@ -108,12 +108,12 @@ public class TaskListDrawable extends Drawable {
|
||||
|
||||
@Override
|
||||
public void setAlpha(@IntRange(from = 0, to = 255) int alpha) {
|
||||
paint.setAlpha(alpha);
|
||||
mPaint.setAlpha(alpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(@Nullable ColorFilter colorFilter) {
|
||||
paint.setColorFilter(colorFilter);
|
||||
mPaint.setColorFilter(colorFilter);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -150,10 +150,10 @@ public class TaskListDrawable extends Drawable {
|
||||
checked = false;
|
||||
}
|
||||
|
||||
final boolean result = checked != isChecked;
|
||||
final boolean result = checked != mIsChecked;
|
||||
if (result) {
|
||||
invalidateSelf();
|
||||
isChecked = checked;
|
||||
mIsChecked = checked;
|
||||
}
|
||||
|
||||
return result;
|
||||
|
@ -12,21 +12,21 @@ import android.text.style.LeadingMarginSpan;
|
||||
*/
|
||||
public class TaskListSpan implements LeadingMarginSpan {
|
||||
|
||||
private final SpannableTheme theme;
|
||||
private final int blockIndent;
|
||||
private final int start;
|
||||
private final boolean isDone;
|
||||
private final SpannableTheme mTheme;
|
||||
private final int mBlockIndent;
|
||||
private final int mStart;
|
||||
private final boolean mIsDone;
|
||||
|
||||
public TaskListSpan(@NonNull SpannableTheme theme, int blockIndent, int start, boolean isDone) {
|
||||
this.theme = theme;
|
||||
this.blockIndent = blockIndent;
|
||||
this.start = start;
|
||||
this.isDone = isDone;
|
||||
mTheme = theme;
|
||||
mBlockIndent = blockIndent;
|
||||
mStart = start;
|
||||
mIsDone = isDone;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLeadingMargin(boolean first) {
|
||||
return theme.getBlockMargin() * blockIndent;
|
||||
return mTheme.getBlockMargin() * mBlockIndent;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -36,7 +36,7 @@ public class TaskListSpan implements LeadingMarginSpan {
|
||||
return;
|
||||
}
|
||||
|
||||
final Drawable drawable = theme.getTaskListDrawable();
|
||||
final Drawable drawable = mTheme.getTaskListDrawable();
|
||||
if (drawable == null) {
|
||||
return;
|
||||
}
|
||||
@ -44,7 +44,7 @@ public class TaskListSpan implements LeadingMarginSpan {
|
||||
final int save = c.save();
|
||||
try {
|
||||
|
||||
final int width = theme.getBlockMargin();
|
||||
final int width = mTheme.getBlockMargin();
|
||||
final int height = bottom - top;
|
||||
|
||||
final int w = (int) (width * .75F + .5F);
|
||||
@ -54,7 +54,7 @@ public class TaskListSpan implements LeadingMarginSpan {
|
||||
|
||||
if (drawable.isStateful()) {
|
||||
final int[] state;
|
||||
if (isDone) {
|
||||
if (mIsDone) {
|
||||
state = new int[]{android.R.attr.state_checked};
|
||||
} else {
|
||||
state = new int[0];
|
||||
@ -62,7 +62,7 @@ public class TaskListSpan implements LeadingMarginSpan {
|
||||
drawable.setState(state);
|
||||
}
|
||||
|
||||
final int l = (width * (blockIndent - 1)) + ((width - w) / 2);
|
||||
final int l = (width * (mBlockIndent - 1)) + ((width - w) / 2);
|
||||
final int t = top + ((height - h) / 2);
|
||||
|
||||
c.translate(l, t);
|
||||
|
@ -9,12 +9,12 @@ 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();
|
||||
private final SpannableTheme mTheme;
|
||||
private final Rect mRect = ObjectsPool.rect();
|
||||
private final Paint mPaint = ObjectsPool.paint();
|
||||
|
||||
public ThematicBreakSpan(@NonNull SpannableTheme theme) {
|
||||
this.theme = theme;
|
||||
mTheme = theme;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -27,13 +27,13 @@ public class ThematicBreakSpan implements LeadingMarginSpan {
|
||||
|
||||
final int middle = top + ((bottom - top) / 2);
|
||||
|
||||
paint.set(p);
|
||||
theme.applyThematicBreakStyle(paint);
|
||||
mPaint.set(p);
|
||||
mTheme.applyThematicBreakStyle(mPaint);
|
||||
|
||||
final int height = (int) (paint.getStrokeWidth() + .5F);
|
||||
final int height = (int) (mPaint.getStrokeWidth() + .5F);
|
||||
final int halfHeight = (int) (height / 2.F + .5F);
|
||||
|
||||
rect.set(x, middle - halfHeight, c.getWidth(), middle + halfHeight);
|
||||
c.drawRect(rect, paint);
|
||||
mRect.set(x, middle - halfHeight, c.getWidth(), middle + halfHeight);
|
||||
c.drawRect(mRect, mPaint);
|
||||
}
|
||||
}
|
||||
|
@ -23,22 +23,22 @@ import java.util.regex.Pattern;
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
class TaskListBlockParser extends AbstractBlockParser {
|
||||
|
||||
private static final Pattern PATTERN = Pattern.compile("\\s*-\\s+\\[(x|X|\\s)\\]\\s+(.*)");
|
||||
private static final Pattern PATTERN = Pattern.compile("\\s*-\\s+\\[(x|X|\\s)]\\s+(.*)");
|
||||
|
||||
private final TaskListBlock block = new TaskListBlock();
|
||||
private final TaskListBlock mBlock = new TaskListBlock();
|
||||
|
||||
private final List<Item> items = new ArrayList<>(3);
|
||||
private final List<Item> mItems = new ArrayList<>(3);
|
||||
|
||||
private int indent = 0;
|
||||
|
||||
TaskListBlockParser(@NonNull String startLine, int startIndent) {
|
||||
items.add(new Item(startLine, startIndent));
|
||||
mItems.add(new Item(startLine, startIndent));
|
||||
indent = startIndent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Block getBlock() {
|
||||
return block;
|
||||
return mBlock;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -69,7 +69,7 @@ class TaskListBlockParser extends AbstractBlockParser {
|
||||
@Override
|
||||
public void addLine(CharSequence line) {
|
||||
if (length(line) > 0) {
|
||||
items.add(new Item(line.toString(), indent));
|
||||
mItems.add(new Item(line.toString(), indent));
|
||||
}
|
||||
}
|
||||
|
||||
@ -80,7 +80,7 @@ class TaskListBlockParser extends AbstractBlockParser {
|
||||
|
||||
TaskListItem listItem;
|
||||
|
||||
for (Item item : items) {
|
||||
for (Item item : mItems) {
|
||||
matcher = PATTERN.matcher(item.line);
|
||||
if (!matcher.matches()) {
|
||||
continue;
|
||||
@ -89,7 +89,7 @@ class TaskListBlockParser extends AbstractBlockParser {
|
||||
.done(isDone(matcher.group(1)))
|
||||
.indent(item.indent / 2);
|
||||
inlineParser.parse(matcher.group(2), listItem);
|
||||
block.appendChild(listItem);
|
||||
mBlock.appendChild(listItem);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,24 +8,24 @@ import org.commonmark.node.CustomNode;
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public class TaskListItem extends CustomNode {
|
||||
|
||||
private boolean done;
|
||||
private int indent;
|
||||
private boolean mDone;
|
||||
private int mIndent;
|
||||
|
||||
public boolean done() {
|
||||
return done;
|
||||
return mDone;
|
||||
}
|
||||
|
||||
public TaskListItem done(boolean done) {
|
||||
this.done = done;
|
||||
mDone = done;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int indent() {
|
||||
return indent;
|
||||
return mIndent;
|
||||
}
|
||||
|
||||
public TaskListItem indent(int indent) {
|
||||
this.indent = indent;
|
||||
mIndent = indent;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user