Merge 35a41818a58405143633fde01de413081ebeb694 into 18938a1aa2e178185de82a4f605d03a0ccb3084d

This commit is contained in:
Peter Gulko 2017-10-26 08:07:21 +00:00 committed by GitHub
commit d55173ad76
38 changed files with 808 additions and 652 deletions

View File

@ -34,6 +34,6 @@ dependencies {
compile OK_HTTP compile OK_HTTP
compile 'com.google.dagger:dagger:2.10' compile 'com.google.dagger:dagger:2.12'
annotationProcessor 'com.google.dagger:dagger-compiler:2.10' annotationProcessor 'com.google.dagger:dagger-compiler:2.12'
} }

View File

@ -4,7 +4,7 @@ buildscript {
google() google()
} }
dependencies { 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 { ext {
// Config // Config
BUILD_TOOLS = '26.0.2' BUILD_TOOLS = '27.0.0'
TARGET_SDK = 26 TARGET_SDK = 27
MIN_SDK = 16 MIN_SDK = 16
// Dependencies // Dependencies
final def supportVersion = '26.1.0' final def supportVersion = '27.0.0'
SUPPORT_ANNOTATIONS = "com.android.support:support-annotations:$supportVersion" SUPPORT_ANNOTATIONS = "com.android.support:support-annotations:$supportVersion"
SUPPORT_APP_COMPAT = "com.android.support:appcompat-v7:$supportVersion" SUPPORT_APP_COMPAT = "com.android.support:appcompat-v7:$supportVersion"
@ -44,5 +44,5 @@ ext {
ANDROID_SVG = 'com.caverock:androidsvg:1.2.1' ANDROID_SVG = 'com.caverock:androidsvg:1.2.1'
ANDROID_GIF = 'pl.droidsonroids.gif:android-gif-drawable:1.2.7' 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'
} }

View File

@ -28,6 +28,8 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import okhttp3.Call; import okhttp3.Call;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
@ -53,39 +55,38 @@ public class AsyncDrawableLoader implements AsyncDrawable.Loader {
private static final String FILE_ANDROID_ASSETS = "android_asset"; private static final String FILE_ANDROID_ASSETS = "android_asset";
private final OkHttpClient client; private final OkHttpClient mClient;
private final Resources resources; private final Resources mResources;
private final ExecutorService executorService; private final ExecutorService mExecutorService;
private final Handler mainThread; private final Handler mMainThread;
private final Drawable errorDrawable; private final Drawable mErrorDrawable;
private final Map<String, Future<?>> requests; private final Map<String, Future<?>> mRequests;
AsyncDrawableLoader(Builder builder) { AsyncDrawableLoader(Builder builder) {
this.client = builder.client; mClient = builder.client;
this.resources = builder.resources; mResources = builder.resources;
this.executorService = builder.executorService; mExecutorService = builder.executorService;
this.mainThread = new Handler(Looper.getMainLooper()); mMainThread = new Handler(Looper.getMainLooper());
this.errorDrawable = builder.errorDrawable; mErrorDrawable = builder.errorDrawable;
this.requests = new HashMap<>(3); mRequests = new HashMap<>(3);
} }
@Override @Override
public void load(@NonNull String destination, @NonNull AsyncDrawable drawable) { public void load(@NonNull String destination, @NonNull AsyncDrawable drawable) {
// if drawable is not a link -> show loading placeholder... // if drawable is not a link -> show loading placeholder...
requests.put(destination, execute(destination, drawable)); mRequests.put(destination, execute(destination, drawable));
} }
@Override @Override
public void cancel(@NonNull String destination) { public void cancel(@NonNull String destination) {
final Future<?> request = requests.remove(destination); final Future<?> request = mRequests.remove(destination);
if (request != null) { if (request != null) {
request.cancel(true); request.cancel(true);
} }
final List<Call> calls = client.dispatcher().queuedCalls(); final List<Call> calls = mClient.dispatcher().queuedCalls();
if (calls != null) { if (calls != null) {
for (Call call : calls) { for (Call call : calls) {
if (!call.isCanceled()) { if (!call.isCanceled()) {
@ -97,14 +98,27 @@ public class AsyncDrawableLoader implements AsyncDrawable.Loader {
} }
} }
private Future<?> execute(@NonNull final String destination, @NonNull AsyncDrawable drawable) { @NonNull
final WeakReference<AsyncDrawable> reference = new WeakReference<AsyncDrawable>(drawable); private Future<?> execute(@NonNull final String destinationCandidate, @NonNull AsyncDrawable drawable) {
final WeakReference<AsyncDrawable> reference = new WeakReference<>(drawable);
// todo, if not a link -> show placeholder // todo, if not a link -> show placeholder
return executorService.submit(new Runnable() { return mExecutorService.submit(new Runnable() {
@Override @Override
public void run() { public void run() {
final Item item; 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); final Uri uri = Uri.parse(destination);
if ("file".equals(uri.getScheme())) { if ("file".equals(uri.getScheme())) {
@ -115,8 +129,7 @@ public class AsyncDrawableLoader implements AsyncDrawable.Loader {
Drawable result = null; Drawable result = null;
if (item != null if (item != null && item.inputStream != null) {
&& item.inputStream != null) {
try { try {
if (CONTENT_TYPE_SVG.equals(item.type)) { if (CONTENT_TYPE_SVG.equals(item.type)) {
result = handleSvg(item.inputStream); 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 is null, we assume it's an error
if (result == null) { if (result == null) {
result = errorDrawable; result = mErrorDrawable;
} }
if (result != null) { if (result != null) {
final Drawable out = result; final Drawable out;
mainThread.post(new Runnable() { if (height != -1 && width != -1) {
out = resize(result, height, width);
} else {
out = result;
}
mMainThread.post(new Runnable() {
@Override @Override
public void run() { public void run() {
final AsyncDrawable asyncDrawable = reference.get(); 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) { private Item fromFile(Uri uri) {
final List<String> segments = uri.getPathSegments(); final List<String> segments = uri.getPathSegments();
if (segments == null if (segments == null || segments.size() == 0) {
|| segments.size() == 0) {
// pointing to file & having no path segments is no use // pointing to file & having no path segments is no use
return null; return null;
} }
@ -192,7 +233,7 @@ public class AsyncDrawableLoader implements AsyncDrawable.Loader {
// load assets // load assets
InputStream inner = null; InputStream inner = null;
try { try {
inner = resources.getAssets().open(path.toString()); inner = mResources.getAssets().open(path.toString());
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
} }
@ -227,7 +268,7 @@ public class AsyncDrawableLoader implements AsyncDrawable.Loader {
Response response = null; Response response = null;
try { try {
response = client.newCall(request).execute(); response = mClient.newCall(request).execute();
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
} }
@ -239,8 +280,7 @@ public class AsyncDrawableLoader implements AsyncDrawable.Loader {
if (inputStream != null) { if (inputStream != null) {
final String type; final String type;
final String contentType = response.header(HEADER_CONTENT_TYPE); final String contentType = response.header(HEADER_CONTENT_TYPE);
if (!TextUtils.isEmpty(contentType) if (!TextUtils.isEmpty(contentType) && contentType.startsWith(CONTENT_TYPE_SVG)) {
&& contentType.startsWith(CONTENT_TYPE_SVG)) {
type = CONTENT_TYPE_SVG; type = CONTENT_TYPE_SVG;
} else { } else {
type = contentType; type = contentType;
@ -270,7 +310,7 @@ public class AsyncDrawableLoader implements AsyncDrawable.Loader {
final float w = svg.getDocumentWidth(); final float w = svg.getDocumentWidth();
final float h = svg.getDocumentHeight(); 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 width = (int) (w * density + .5F);
final int height = (int) (h * density + .5F); final int height = (int) (h * density + .5F);
@ -280,7 +320,7 @@ public class AsyncDrawableLoader implements AsyncDrawable.Loader {
canvas.scale(density, density); canvas.scale(density, density);
svg.renderToCanvas(canvas); svg.renderToCanvas(canvas);
out = new BitmapDrawable(resources, bitmap); out = new BitmapDrawable(mResources, bitmap);
DrawableUtils.intrinsicBounds(out); DrawableUtils.intrinsicBounds(out);
} }
@ -310,7 +350,7 @@ public class AsyncDrawableLoader implements AsyncDrawable.Loader {
final Bitmap bitmap = BitmapFactory.decodeStream(stream); final Bitmap bitmap = BitmapFactory.decodeStream(stream);
if (bitmap != null) { if (bitmap != null) {
out = new BitmapDrawable(resources, bitmap); out = new BitmapDrawable(mResources, bitmap);
DrawableUtils.intrinsicBounds(out); DrawableUtils.intrinsicBounds(out);
} else { } else {
out = null; out = null;

View File

@ -9,17 +9,17 @@ import ru.noties.markwon.view.IMarkwonView;
public class DebugConfigurationProvider implements IMarkwonView.ConfigurationProvider { public class DebugConfigurationProvider implements IMarkwonView.ConfigurationProvider {
private SpannableConfiguration cached; private SpannableConfiguration mCached;
@NonNull @NonNull
@Override @Override
public SpannableConfiguration provide(@NonNull Context context) { public SpannableConfiguration provide(@NonNull Context context) {
if (cached == null) { if (mCached == null) {
cached = SpannableConfiguration.builder(context) mCached = SpannableConfiguration.builder(context)
.theme(debugTheme(context)) .theme(debugTheme(context))
.build(); .build();
} }
return cached; return mCached;
} }
private static SpannableTheme debugTheme(@NonNull Context context) { private static SpannableTheme debugTheme(@NonNull Context context) {

View File

@ -12,7 +12,7 @@ import ru.noties.markwon.SpannableConfiguration;
@SuppressLint("AppCompatCustomView") @SuppressLint("AppCompatCustomView")
public class MarkwonView extends TextView implements IMarkwonView { public class MarkwonView extends TextView implements IMarkwonView {
private MarkwonViewHelper helper; private MarkwonViewHelper mHelper;
public MarkwonView(Context context) { public MarkwonView(Context context) {
super(context); super(context);
@ -25,26 +25,26 @@ public class MarkwonView extends TextView implements IMarkwonView {
} }
private void init(Context context, AttributeSet attributeSet) { private void init(Context context, AttributeSet attributeSet) {
helper = MarkwonViewHelper.create(this); mHelper = MarkwonViewHelper.create(this);
helper.init(context, attributeSet); mHelper.init(context, attributeSet);
} }
@Override @Override
public void setConfigurationProvider(@NonNull ConfigurationProvider provider) { public void setConfigurationProvider(@NonNull ConfigurationProvider provider) {
helper.setConfigurationProvider(provider); mHelper.setConfigurationProvider(provider);
} }
public void setMarkdown(@Nullable String markdown) { public void setMarkdown(@Nullable String markdown) {
helper.setMarkdown(markdown); mHelper.setMarkdown(markdown);
} }
public void setMarkdown(@Nullable SpannableConfiguration configuration, @Nullable String markdown) { public void setMarkdown(@Nullable SpannableConfiguration configuration, @Nullable String markdown) {
helper.setMarkdown(configuration, markdown); mHelper.setMarkdown(configuration, markdown);
} }
@Nullable @Nullable
@Override @Override
public String getMarkdown() { public String getMarkdown() {
return helper.getMarkdown(); return mHelper.getMarkdown();
} }
} }

View File

@ -10,7 +10,7 @@ import ru.noties.markwon.SpannableConfiguration;
public class MarkwonViewCompat extends AppCompatTextView implements IMarkwonView { public class MarkwonViewCompat extends AppCompatTextView implements IMarkwonView {
private MarkwonViewHelper helper; private MarkwonViewHelper mHelper;
public MarkwonViewCompat(Context context) { public MarkwonViewCompat(Context context) {
super(context); super(context);
@ -23,28 +23,28 @@ public class MarkwonViewCompat extends AppCompatTextView implements IMarkwonView
} }
private void init(Context context, AttributeSet attributeSet) { private void init(Context context, AttributeSet attributeSet) {
helper = MarkwonViewHelper.create(this); mHelper = MarkwonViewHelper.create(this);
helper.init(context, attributeSet); mHelper.init(context, attributeSet);
} }
@Override @Override
public void setConfigurationProvider(@NonNull ConfigurationProvider provider) { public void setConfigurationProvider(@NonNull ConfigurationProvider provider) {
helper.setConfigurationProvider(provider); mHelper.setConfigurationProvider(provider);
} }
@Override @Override
public void setMarkdown(@Nullable String markdown) { public void setMarkdown(@Nullable String markdown) {
helper.setMarkdown(markdown); mHelper.setMarkdown(markdown);
} }
@Override @Override
public void setMarkdown(@Nullable SpannableConfiguration configuration, @Nullable String markdown) { public void setMarkdown(@Nullable SpannableConfiguration configuration, @Nullable String markdown) {
helper.setMarkdown(configuration, markdown); mHelper.setMarkdown(configuration, markdown);
} }
@Nullable @Nullable
@Override @Override
public String getMarkdown() { public String getMarkdown() {
return helper.getMarkdown(); return mHelper.getMarkdown();
} }
} }

View File

@ -17,15 +17,15 @@ public class MarkwonViewHelper implements IMarkwonView {
return new MarkwonViewHelper(view); return new MarkwonViewHelper(view);
} }
private final TextView textView; private final TextView mTextView;
private ConfigurationProvider provider; private ConfigurationProvider mProvider;
private SpannableConfiguration configuration; private SpannableConfiguration mConfiguration;
private String markdown; private String mMarkdown;
private MarkwonViewHelper(@NonNull TextView textView) { private MarkwonViewHelper(@NonNull TextView textView) {
this.textView = textView; mTextView = textView;
} }
public void init(Context context, AttributeSet attributeSet) { public void init(Context context, AttributeSet attributeSet) {
@ -57,11 +57,11 @@ public class MarkwonViewHelper implements IMarkwonView {
@Override @Override
public void setConfigurationProvider(@NonNull ConfigurationProvider provider) { public void setConfigurationProvider(@NonNull ConfigurationProvider provider) {
this.provider = provider; mProvider = provider;
this.configuration = provider.provide(textView.getContext()); mConfiguration = provider.provide(mTextView.getContext());
if (!TextUtils.isEmpty(markdown)) { if (!TextUtils.isEmpty(mMarkdown)) {
// invalidate rendered markdown // invalidate rendered markdown
setMarkdown(markdown); setMarkdown(mMarkdown);
} }
} }
@ -72,24 +72,24 @@ public class MarkwonViewHelper implements IMarkwonView {
@Override @Override
public void setMarkdown(@Nullable SpannableConfiguration configuration, @Nullable String markdown) { public void setMarkdown(@Nullable SpannableConfiguration configuration, @Nullable String markdown) {
this.markdown = markdown; mMarkdown = markdown;
if (configuration == null) { if (configuration == null) {
if (this.configuration == null) { if (mConfiguration == null) {
if (provider != null) { if (mProvider != null) {
this.configuration = provider.provide(textView.getContext()); mConfiguration = mProvider.provide(mTextView.getContext());
} else { } 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 @Nullable
@Override @Override
public String getMarkdown() { public String getMarkdown() {
return markdown; return mMarkdown;
} }
@Nullable @Nullable

View File

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

View File

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

View File

@ -12,6 +12,7 @@ import android.view.View;
import ru.noties.markwon.spans.LinkSpan; import ru.noties.markwon.spans.LinkSpan;
public class LinkResolverDef implements LinkSpan.Resolver { public class LinkResolverDef implements LinkSpan.Resolver {
@Override @Override
public void resolve(View view, @NonNull String link) { public void resolve(View view, @NonNull String link) {
final Uri uri = Uri.parse(link); final Uri uri = Uri.parse(link);

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

View File

@ -20,44 +20,50 @@ public class SpannableConfiguration {
return new Builder(context); return new Builder(context);
} }
private final SpannableTheme theme; private final SpannableTheme mTheme;
private final AsyncDrawable.Loader asyncDrawableLoader; private final AsyncDrawable.Loader mAsyncDrawableLoader;
private final SyntaxHighlight syntaxHighlight; private final SyntaxHighlight mSyntaxHighlight;
private final LinkSpan.Resolver linkResolver; private final LinkSpan.Resolver mLinkResolver;
private final UrlProcessor urlProcessor; private final UrlProcessor mUrlProcessor;
private final SpannableHtmlParser htmlParser; private final SpannableHtmlParser mHtmlParser;
private final ImageClickResolver mImageClickResolver;
private SpannableConfiguration(Builder builder) { private SpannableConfiguration(Builder builder) {
this.theme = builder.theme; mTheme = builder.theme;
this.asyncDrawableLoader = builder.asyncDrawableLoader; mAsyncDrawableLoader = builder.asyncDrawableLoader;
this.syntaxHighlight = builder.syntaxHighlight; mSyntaxHighlight = builder.syntaxHighlight;
this.linkResolver = builder.linkResolver; mLinkResolver = builder.linkResolver;
this.urlProcessor = builder.urlProcessor; mUrlProcessor = builder.urlProcessor;
this.htmlParser = builder.htmlParser; mHtmlParser = builder.htmlParser;
mImageClickResolver = builder.imageClickResolver;
} }
public SpannableTheme theme() { public SpannableTheme theme() {
return theme; return mTheme;
} }
public AsyncDrawable.Loader asyncDrawableLoader() { public AsyncDrawable.Loader asyncDrawableLoader() {
return asyncDrawableLoader; return mAsyncDrawableLoader;
} }
public SyntaxHighlight syntaxHighlight() { public SyntaxHighlight syntaxHighlight() {
return syntaxHighlight; return mSyntaxHighlight;
} }
public LinkSpan.Resolver linkResolver() { public LinkSpan.Resolver linkResolver() {
return linkResolver; return mLinkResolver;
} }
public UrlProcessor urlProcessor() { public UrlProcessor urlProcessor() {
return urlProcessor; return mUrlProcessor;
} }
public SpannableHtmlParser htmlParser() { public SpannableHtmlParser htmlParser() {
return htmlParser; return mHtmlParser;
}
public ImageClickResolver imageClickResolver() {
return mImageClickResolver;
} }
public static class Builder { public static class Builder {
@ -69,6 +75,7 @@ public class SpannableConfiguration {
private LinkSpan.Resolver linkResolver; private LinkSpan.Resolver linkResolver;
private UrlProcessor urlProcessor; private UrlProcessor urlProcessor;
private SpannableHtmlParser htmlParser; private SpannableHtmlParser htmlParser;
private ImageClickResolver imageClickResolver;
Builder(Context context) { Builder(Context context) {
this.context = context; this.context = context;
@ -104,6 +111,11 @@ public class SpannableConfiguration {
return this; return this;
} }
public Builder setImageClickResolver(ImageClickResolver imageClickResolver) {
this.imageClickResolver = imageClickResolver;
return this;
}
public SpannableConfiguration build() { public SpannableConfiguration build() {
if (theme == null) { if (theme == null) {
theme = SpannableTheme.create(context); theme = SpannableTheme.create(context);
@ -120,8 +132,12 @@ public class SpannableConfiguration {
if (urlProcessor == null) { if (urlProcessor == null) {
urlProcessor = new UrlProcessorNoOp(); urlProcessor = new UrlProcessorNoOp();
} }
if (imageClickResolver == null) {
imageClickResolver = new ImageClickResolverDef();
}
if (htmlParser == null) { if (htmlParser == null) {
htmlParser = SpannableHtmlParser.create(theme, asyncDrawableLoader, urlProcessor, linkResolver); htmlParser = SpannableHtmlParser.create(theme, asyncDrawableLoader, urlProcessor,
linkResolver, imageClickResolver);
} }
return new SpannableConfiguration(this); return new SpannableConfiguration(this);
} }

View File

@ -13,8 +13,7 @@ abstract class TableRowsScheduler {
static void schedule(@NonNull final TextView view) { static void schedule(@NonNull final TextView view) {
final Object[] spans = extract(view); final Object[] spans = extract(view);
if (spans != null if (spans != null && spans.length > 0) {
&& spans.length > 0) {
if (view.getTag(R.id.markwon_tables_scheduler) == null) { if (view.getTag(R.id.markwon_tables_scheduler) == null) {
final View.OnAttachStateChangeListener listener = new View.OnAttachStateChangeListener() { final View.OnAttachStateChangeListener listener = new View.OnAttachStateChangeListener() {

View File

@ -7,17 +7,17 @@ import android.text.TextUtils;
public class UrlProcessorAndroidAssets implements UrlProcessor { public class UrlProcessorAndroidAssets implements UrlProcessor {
private final UrlProcessorRelativeToAbsolute assetsProcessor private final UrlProcessorRelativeToAbsolute mAssetsProcessor
= new UrlProcessorRelativeToAbsolute("file:///android_asset/"); = new UrlProcessorRelativeToAbsolute("file:///android_asset/");
private final UrlProcessor processor; private final UrlProcessor mProcessor;
public UrlProcessorAndroidAssets() { public UrlProcessorAndroidAssets() {
this(null); this(null);
} }
public UrlProcessorAndroidAssets(@Nullable UrlProcessor parent) { public UrlProcessorAndroidAssets(@Nullable UrlProcessor parent) {
this.processor = parent; mProcessor = parent;
} }
@NonNull @NonNull
@ -26,10 +26,10 @@ public class UrlProcessorAndroidAssets implements UrlProcessor {
final String out; final String out;
final Uri uri = Uri.parse(destination); final Uri uri = Uri.parse(destination);
if (TextUtils.isEmpty(uri.getScheme())) { if (TextUtils.isEmpty(uri.getScheme())) {
out = assetsProcessor.process(destination); out = mAssetsProcessor.process(destination);
} else { } else {
if (processor != null) { if (mProcessor != null) {
out = processor.process(destination); out = mProcessor.process(destination);
} else { } else {
out = destination; out = destination;
} }

View File

@ -9,10 +9,10 @@ import java.net.URL;
@SuppressWarnings("WeakerAccess") @SuppressWarnings("WeakerAccess")
public class UrlProcessorRelativeToAbsolute implements UrlProcessor { public class UrlProcessorRelativeToAbsolute implements UrlProcessor {
private final URL base; private final URL mBase;
public UrlProcessorRelativeToAbsolute(@NonNull String base) { public UrlProcessorRelativeToAbsolute(@NonNull String base) {
this.base = obtain(base); mBase = obtain(base);
} }
@NonNull @NonNull
@ -21,9 +21,9 @@ public class UrlProcessorRelativeToAbsolute implements UrlProcessor {
String out = destination; String out = destination;
if (base != null) { if (mBase != null) {
try { try {
final URL u = new URL(base, destination); final URL u = new URL(mBase, destination);
out = u.toString(); out = u.toString();
} catch (MalformedURLException e) { } catch (MalformedURLException e) {
e.printStackTrace(); e.printStackTrace();

View File

@ -4,7 +4,9 @@ import android.support.annotation.NonNull;
import android.text.SpannableStringBuilder; import android.text.SpannableStringBuilder;
import android.text.Spanned; import android.text.Spanned;
import android.text.TextUtils; import android.text.TextUtils;
import android.text.style.ClickableSpan;
import android.text.style.StrikethroughSpan; import android.text.style.StrikethroughSpan;
import android.view.View;
import org.commonmark.ext.gfm.strikethrough.Strikethrough; import org.commonmark.ext.gfm.strikethrough.Strikethrough;
import org.commonmark.ext.gfm.tables.TableBody; import org.commonmark.ext.gfm.tables.TableBody;
@ -60,41 +62,41 @@ import ru.noties.markwon.tasklist.TaskListItem;
@SuppressWarnings("WeakerAccess") @SuppressWarnings("WeakerAccess")
public class SpannableMarkdownVisitor extends AbstractVisitor { public class SpannableMarkdownVisitor extends AbstractVisitor {
private final SpannableConfiguration configuration; private final SpannableConfiguration mConfiguration;
private final SpannableStringBuilder builder; private final SpannableStringBuilder mBuilder;
private final Deque<HtmlInlineItem> htmlInlineItems; private final Deque<HtmlInlineItem> mHtmlInlineItems;
private int blockQuoteIndent; private int mBlockQuoteIndent;
private int listLevel; private int mListLevel;
private List<TableRowSpan.Cell> pendingTableRow; private List<TableRowSpan.Cell> mPendingTableRow;
private boolean tableRowIsHeader; private boolean mTableRowIsHeader;
private int tableRows; private int mTableRows;
public SpannableMarkdownVisitor( public SpannableMarkdownVisitor(
@NonNull SpannableConfiguration configuration, @NonNull SpannableConfiguration configuration,
@NonNull SpannableStringBuilder builder @NonNull SpannableStringBuilder builder
) { ) {
this.configuration = configuration; mConfiguration = configuration;
this.builder = builder; mBuilder = builder;
this.htmlInlineItems = new ArrayDeque<>(2); mHtmlInlineItems = new ArrayDeque<>(2);
} }
@Override @Override
public void visit(Text text) { public void visit(Text text) {
builder.append(text.getLiteral()); mBuilder.append(text.getLiteral());
} }
@Override @Override
public void visit(StrongEmphasis strongEmphasis) { public void visit(StrongEmphasis strongEmphasis) {
final int length = builder.length(); final int length = mBuilder.length();
visitChildren(strongEmphasis); visitChildren(strongEmphasis);
setSpan(length, new StrongEmphasisSpan()); setSpan(length, new StrongEmphasisSpan());
} }
@Override @Override
public void visit(Emphasis emphasis) { public void visit(Emphasis emphasis) {
final int length = builder.length(); final int length = mBuilder.length();
visitChildren(emphasis); visitChildren(emphasis);
setSpan(length, new EmphasisSpan()); setSpan(length, new EmphasisSpan());
} }
@ -103,42 +105,42 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
public void visit(BlockQuote blockQuote) { public void visit(BlockQuote blockQuote) {
newLine(); newLine();
if (blockQuoteIndent != 0) { if (mBlockQuoteIndent != 0) {
builder.append('\n'); mBuilder.append('\n');
} }
final int length = builder.length(); final int length = mBuilder.length();
blockQuoteIndent += 1; mBlockQuoteIndent += 1;
visitChildren(blockQuote); visitChildren(blockQuote);
setSpan(length, new BlockQuoteSpan( setSpan(length, new BlockQuoteSpan(
configuration.theme(), mConfiguration.theme(),
blockQuoteIndent mBlockQuoteIndent
)); ));
blockQuoteIndent -= 1; mBlockQuoteIndent -= 1;
newLine(); newLine();
if (blockQuoteIndent == 0) { if (mBlockQuoteIndent == 0) {
builder.append('\n'); mBuilder.append('\n');
} }
} }
@Override @Override
public void visit(Code code) { 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 // 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 // unfortunately we cannot use this for multiline code as we cannot control where a new line break will be inserted
builder.append('\u00a0'); mBuilder.append('\u00a0');
builder.append(code.getLiteral()); mBuilder.append(code.getLiteral());
builder.append('\u00a0'); mBuilder.append('\u00a0');
setSpan(length, new CodeSpan( setSpan(length, new CodeSpan(
configuration.theme(), mConfiguration.theme(),
false false
)); ));
} }
@ -148,23 +150,23 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
newLine(); newLine();
final int length = builder.length(); final int length = mBuilder.length();
// empty lines on top & bottom // empty lines on top & bottom
builder.append('\u00a0').append('\n'); mBuilder.append('\u00a0').append('\n');
builder.append( mBuilder.append(
configuration.syntaxHighlight() mConfiguration.syntaxHighlight()
.highlight(fencedCodeBlock.getInfo(), fencedCodeBlock.getLiteral()) .highlight(fencedCodeBlock.getInfo(), fencedCodeBlock.getLiteral())
); );
builder.append('\u00a0').append('\n'); mBuilder.append('\u00a0').append('\n');
setSpan(length, new CodeSpan( setSpan(length, new CodeSpan(
configuration.theme(), mConfiguration.theme(),
true true
)); ));
newLine(); newLine();
builder.append('\n'); mBuilder.append('\n');
} }
@Override @Override
@ -181,18 +183,18 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
newLine(); newLine();
visitChildren(node); visitChildren(node);
newLine(); newLine();
if (listLevel == 0 && blockQuoteIndent == 0) { if (mListLevel == 0 && mBlockQuoteIndent == 0) {
builder.append('\n'); mBuilder.append('\n');
} }
} }
@Override @Override
public void visit(ListItem listItem) { public void visit(ListItem listItem) {
final int length = builder.length(); final int length = mBuilder.length();
blockQuoteIndent += 1; mBlockQuoteIndent += 1;
listLevel += 1; mListLevel += 1;
final Node parent = listItem.getParent(); final Node parent = listItem.getParent();
if (parent instanceof OrderedList) { if (parent instanceof OrderedList) {
@ -202,9 +204,9 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
visitChildren(listItem); visitChildren(listItem);
setSpan(length, new OrderedListItemSpan( setSpan(length, new OrderedListItemSpan(
configuration.theme(), mConfiguration.theme(),
String.valueOf(start) + "." + '\u00a0', String.valueOf(start) + "." + '\u00a0',
blockQuoteIndent mBlockQuoteIndent
)); ));
// after we have visited the children increment start number // after we have visited the children increment start number
@ -216,14 +218,14 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
visitChildren(listItem); visitChildren(listItem);
setSpan(length, new BulletListItemSpan( setSpan(length, new BulletListItemSpan(
configuration.theme(), mConfiguration.theme(),
blockQuoteIndent, mBlockQuoteIndent,
listLevel - 1 mListLevel - 1
)); ));
} }
blockQuoteIndent -= 1; mBlockQuoteIndent -= 1;
listLevel -= 1; mListLevel -= 1;
newLine(); newLine();
} }
@ -233,12 +235,12 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
newLine(); newLine();
final int length = builder.length(); final int length = mBuilder.length();
builder.append(' '); // without space it won't render mBuilder.append(' '); // without space it won't render
setSpan(length, new ThematicBreakSpan(configuration.theme())); setSpan(length, new ThematicBreakSpan(mConfiguration.theme()));
newLine(); newLine();
builder.append('\n'); mBuilder.append('\n');
} }
@Override @Override
@ -246,24 +248,24 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
newLine(); newLine();
final int length = builder.length(); final int length = mBuilder.length();
visitChildren(heading); visitChildren(heading);
setSpan(length, new HeadingSpan( setSpan(length, new HeadingSpan(
configuration.theme(), mConfiguration.theme(),
heading.getLevel(), heading.getLevel(),
builder.length() - length) mBuilder.length() - length)
); );
newLine(); newLine();
// after heading we add another line anyway (no additional checks) // after heading we add another line anyway (no additional checks)
builder.append('\n'); mBuilder.append('\n');
} }
@Override @Override
public void visit(SoftLineBreak softLineBreak) { public void visit(SoftLineBreak softLineBreak) {
// at first here was a new line, but here should be a space char // at first here was a new line, but here should be a space char
builder.append(' '); mBuilder.append(' ');
} }
@Override @Override
@ -277,11 +279,11 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
@Override @Override
public void visit(CustomBlock customBlock) { public void visit(CustomBlock customBlock) {
if (customBlock instanceof TaskListBlock) { if (customBlock instanceof TaskListBlock) {
blockQuoteIndent += 1; mBlockQuoteIndent += 1;
visitChildren(customBlock); visitChildren(customBlock);
blockQuoteIndent -= 1; mBlockQuoteIndent -= 1;
newLine(); newLine();
builder.append('\n'); mBuilder.append('\n');
} else { } else {
super.visit(customBlock); super.visit(customBlock);
} }
@ -292,7 +294,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
if (customNode instanceof Strikethrough) { if (customNode instanceof Strikethrough) {
final int length = builder.length(); final int length = mBuilder.length();
visitChildren(customNode); visitChildren(customNode);
setSpan(length, new StrikethroughSpan()); setSpan(length, new StrikethroughSpan());
@ -302,22 +304,22 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
final TaskListItem listItem = (TaskListItem) customNode; final TaskListItem listItem = (TaskListItem) customNode;
final int length = builder.length(); final int length = mBuilder.length();
blockQuoteIndent += listItem.indent(); mBlockQuoteIndent += listItem.indent();
visitChildren(customNode); visitChildren(customNode);
setSpan(length, new TaskListSpan( setSpan(length, new TaskListSpan(
configuration.theme(), mConfiguration.theme(),
blockQuoteIndent, mBlockQuoteIndent,
length, length,
listItem.done() listItem.done()
)); ));
newLine(); newLine();
blockQuoteIndent -= listItem.indent(); mBlockQuoteIndent -= listItem.indent();
} else if (!handleTableNodes(customNode)) { } else if (!handleTableNodes(customNode)) {
super.visit(customNode); super.visit(customNode);
@ -330,50 +332,50 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
if (node instanceof TableBody) { if (node instanceof TableBody) {
visitChildren(node); visitChildren(node);
tableRows = 0; mTableRows = 0;
handled = true; handled = true;
newLine(); newLine();
builder.append('\n'); mBuilder.append('\n');
} else if (node instanceof TableRow) { } else if (node instanceof TableRow) {
final int length = builder.length(); final int length = mBuilder.length();
visitChildren(node); visitChildren(node);
if (pendingTableRow != null) { if (mPendingTableRow != null) {
builder.append(' '); mBuilder.append(' ');
final TableRowSpan span = new TableRowSpan( final TableRowSpan span = new TableRowSpan(
configuration.theme(), mConfiguration.theme(),
pendingTableRow, mPendingTableRow,
tableRowIsHeader, mTableRowIsHeader,
tableRows % 2 == 1 mTableRows % 2 == 1
); );
tableRows = tableRowIsHeader mTableRows = mTableRowIsHeader
? 0 ? 0
: tableRows + 1; : mTableRows + 1;
setSpan(length, span); setSpan(length, span);
newLine(); newLine();
pendingTableRow = null; mPendingTableRow = null;
} }
handled = true; handled = true;
} else if (node instanceof TableCell) { } else if (node instanceof TableCell) {
final TableCell cell = (TableCell) node; final TableCell cell = (TableCell) node;
final int length = builder.length(); final int length = mBuilder.length();
visitChildren(cell); visitChildren(cell);
if (pendingTableRow == null) { if (mPendingTableRow == null) {
pendingTableRow = new ArrayList<>(2); mPendingTableRow = new ArrayList<>(2);
} }
pendingTableRow.add(new TableRowSpan.Cell( mPendingTableRow.add(new TableRowSpan.Cell(
tableCellAlignment(cell.getAlignment()), 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; handled = true;
} else { } else {
@ -396,8 +398,8 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
if (!inTightList) { if (!inTightList) {
newLine(); newLine();
if (blockQuoteIndent == 0) { if (mBlockQuoteIndent == 0) {
builder.append('\n'); mBuilder.append('\n');
} }
} }
} }
@ -405,49 +407,53 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
@Override @Override
public void visit(Image image) { public void visit(Image image) {
final int length = builder.length(); final int length = mBuilder.length();
visitChildren(image); visitChildren(image);
// we must check if anything _was_ added, as we need at least one char to render // we must check if anything _was_ added, as we need at least one char to render
if (length == builder.length()) { if (length == mBuilder.length()) {
builder.append('\uFFFC'); mBuilder.append('\uFFFC');
} }
final Node parent = image.getParent(); final Node parent = image.getParent();
final boolean link = parent != null && parent instanceof Link; 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( setSpan(
length, length,
new AsyncDrawableSpan( new AsyncDrawableSpan(
configuration.theme(), mConfiguration.theme(),
new AsyncDrawable( new AsyncDrawable(
destination, destination,
configuration.asyncDrawableLoader() mConfiguration.asyncDrawableLoader()
), ),
AsyncDrawableSpan.ALIGN_BOTTOM, AsyncDrawableSpan.ALIGN_BOTTOM,
link link
) )
); );
// todo, maybe, if image is not inside a link, we should make it clickable, so setSpan(length, new ClickableSpan() {
// user can open it in external viewer? @Override
public void onClick(View view) {
mConfiguration.imageClickResolver().resolve(view, destination);
}
});
} }
@Override @Override
public void visit(HtmlBlock htmlBlock) { public void visit(HtmlBlock htmlBlock) {
// http://spec.commonmark.org/0.18/#html-blocks // 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)) { if (!TextUtils.isEmpty(spanned)) {
builder.append(spanned); mBuilder.append(spanned);
} }
} }
@Override @Override
public void visit(HtmlInline htmlInline) { public void visit(HtmlInline htmlInline) {
final SpannableHtmlParser htmlParser = configuration.htmlParser(); final SpannableHtmlParser htmlParser = mConfiguration.htmlParser();
final SpannableHtmlParser.Tag tag = htmlParser.parseTag(htmlInline.getLiteral()); final SpannableHtmlParser.Tag tag = htmlParser.parseTag(htmlInline.getLiteral());
if (tag != null) { if (tag != null) {
@ -455,13 +461,13 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
final boolean voidTag = tag.voidTag(); final boolean voidTag = tag.voidTag();
if (!voidTag && tag.opening()) { if (!voidTag && tag.opening()) {
// push in stack // push in stack
htmlInlineItems.push(new HtmlInlineItem(tag, builder.length())); mHtmlInlineItems.push(new HtmlInlineItem(tag, mBuilder.length()));
visitChildren(htmlInline); visitChildren(htmlInline);
} else { } else {
if (!voidTag) { if (!voidTag) {
if (htmlInlineItems.size() > 0) { if (mHtmlInlineItems.size() > 0) {
final HtmlInlineItem item = htmlInlineItems.pop(); final HtmlInlineItem item = mHtmlInlineItems.pop();
final Object span = htmlParser.getSpanForTag(item.tag); final Object span = htmlParser.getSpanForTag(item.tag);
if (span != null) { if (span != null) {
setSpan(item.start, span); setSpan(item.start, span);
@ -471,34 +477,34 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
final Spanned html = htmlParser.getSpanned(tag, htmlInline.getLiteral()); final Spanned html = htmlParser.getSpanned(tag, htmlInline.getLiteral());
if (!TextUtils.isEmpty(html)) { if (!TextUtils.isEmpty(html)) {
builder.append(html); mBuilder.append(html);
} }
} }
} }
} else { } else {
// todo, should we append just literal? // todo, should we append just literal?
// builder.append(htmlInline.getLiteral()); // mBuilder.append(htmlInline.getLiteral());
visitChildren(htmlInline); visitChildren(htmlInline);
} }
} }
@Override @Override
public void visit(Link link) { public void visit(Link link) {
final int length = builder.length(); final int length = mBuilder.length();
visitChildren(link); visitChildren(link);
final String destination = configuration.urlProcessor().process(link.getDestination()); final String destination = mConfiguration.urlProcessor().process(link.getDestination());
setSpan(length, new LinkSpan(configuration.theme(), destination, configuration.linkResolver())); setSpan(length, new LinkSpan(mConfiguration.theme(), destination, mConfiguration.linkResolver()));
} }
private void setSpan(int start, @NonNull Object span) { 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() { private void newLine() {
if (builder.length() > 0 if (mBuilder.length() > 0
&& '\n' != builder.charAt(builder.length() - 1)) { && '\n' != mBuilder.charAt(mBuilder.length() - 1)) {
builder.append('\n'); mBuilder.append('\n');
} }
} }

View File

@ -4,9 +4,12 @@ import android.support.annotation.NonNull;
import android.text.SpannableString; import android.text.SpannableString;
import android.text.Spanned; import android.text.Spanned;
import android.text.TextUtils; import android.text.TextUtils;
import android.text.style.ClickableSpan;
import android.view.View;
import java.util.Map; import java.util.Map;
import ru.noties.markwon.ImageClickResolver;
import ru.noties.markwon.UrlProcessor; import ru.noties.markwon.UrlProcessor;
import ru.noties.markwon.spans.AsyncDrawable; import ru.noties.markwon.spans.AsyncDrawable;
import ru.noties.markwon.spans.AsyncDrawableSpan; import ru.noties.markwon.spans.AsyncDrawableSpan;
@ -14,17 +17,20 @@ import ru.noties.markwon.spans.SpannableTheme;
class ImageProviderImpl implements SpannableHtmlParser.ImageProvider { class ImageProviderImpl implements SpannableHtmlParser.ImageProvider {
private final SpannableTheme theme; private final SpannableTheme mTheme;
private final AsyncDrawable.Loader loader; private final AsyncDrawable.Loader mLoader;
private final UrlProcessor urlProcessor; private final UrlProcessor mUrlProcessor;
private final ImageClickResolver mImageClickResolver;
ImageProviderImpl( ImageProviderImpl(
@NonNull SpannableTheme theme, @NonNull SpannableTheme theme,
@NonNull AsyncDrawable.Loader loader, @NonNull AsyncDrawable.Loader loader,
@NonNull UrlProcessor urlProcessor) { @NonNull UrlProcessor urlProcessor,
this.theme = theme; @NonNull ImageClickResolver imageClickResolver) {
this.loader = loader; mTheme = theme;
this.urlProcessor = urlProcessor; mLoader = loader;
mUrlProcessor = urlProcessor;
mImageClickResolver = imageClickResolver;
} }
@Override @Override
@ -38,7 +44,7 @@ class ImageProviderImpl implements SpannableHtmlParser.ImageProvider {
if (!TextUtils.isEmpty(src)) { if (!TextUtils.isEmpty(src)) {
final String destination = urlProcessor.process(src); final String destination = mUrlProcessor.process(src);
final String replacement; final String replacement;
if (!TextUtils.isEmpty(alt)) { if (!TextUtils.isEmpty(alt)) {
@ -47,12 +53,20 @@ class ImageProviderImpl implements SpannableHtmlParser.ImageProvider {
replacement = "\uFFFC"; replacement = "\uFFFC";
} }
final AsyncDrawable drawable = new AsyncDrawable(destination, loader); final AsyncDrawable drawable = new AsyncDrawable(destination, mLoader);
final AsyncDrawableSpan span = new AsyncDrawableSpan(theme, drawable); final AsyncDrawableSpan span = new AsyncDrawableSpan(mTheme, drawable);
final SpannableString string = new SpannableString(replacement); final SpannableString string = new SpannableString(replacement);
string.setSpan(span, 0, string.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 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; spanned = string;
} else { } else {
spanned = null; spanned = null;

View File

@ -11,17 +11,17 @@ import ru.noties.markwon.spans.SpannableTheme;
class LinkProvider implements SpannableHtmlParser.SpanProvider { class LinkProvider implements SpannableHtmlParser.SpanProvider {
private final SpannableTheme theme; private final SpannableTheme mTheme;
private final UrlProcessor urlProcessor; private final UrlProcessor mUrlProcessor;
private final LinkSpan.Resolver resolver; private final LinkSpan.Resolver mResolver;
LinkProvider( LinkProvider(
@NonNull SpannableTheme theme, @NonNull SpannableTheme theme,
@NonNull UrlProcessor urlProcessor, @NonNull UrlProcessor urlProcessor,
@NonNull LinkSpan.Resolver resolver) { @NonNull LinkSpan.Resolver resolver) {
this.theme = theme; mTheme = theme;
this.urlProcessor = urlProcessor; mUrlProcessor = urlProcessor;
this.resolver = resolver; mResolver = resolver;
} }
@Override @Override
@ -33,8 +33,8 @@ class LinkProvider implements SpannableHtmlParser.SpanProvider {
final String href = attributes.get("href"); final String href = attributes.get("href");
if (!TextUtils.isEmpty(href)) { if (!TextUtils.isEmpty(href)) {
final String destination = urlProcessor.process(href); final String destination = mUrlProcessor.process(href);
span = new LinkSpan(theme, destination, resolver); span = new LinkSpan(mTheme, destination, mResolver);
} else { } else {
span = null; span = null;

View File

@ -10,6 +10,8 @@ import android.text.Spanned;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import ru.noties.markwon.ImageClickResolver;
import ru.noties.markwon.ImageClickResolverDef;
import ru.noties.markwon.LinkResolverDef; import ru.noties.markwon.LinkResolverDef;
import ru.noties.markwon.UrlProcessor; import ru.noties.markwon.UrlProcessor;
import ru.noties.markwon.UrlProcessorNoOp; import ru.noties.markwon.UrlProcessorNoOp;
@ -25,7 +27,8 @@ public class SpannableHtmlParser {
@NonNull SpannableTheme theme, @NonNull SpannableTheme theme,
@NonNull AsyncDrawable.Loader loader @NonNull AsyncDrawable.Loader loader
) { ) {
return builderWithDefaults(theme, loader, null, null) return builderWithDefaults(theme, loader, null, null,
null)
.build(); .build();
} }
@ -33,9 +36,10 @@ public class SpannableHtmlParser {
@NonNull SpannableTheme theme, @NonNull SpannableTheme theme,
@NonNull AsyncDrawable.Loader loader, @NonNull AsyncDrawable.Loader loader,
@NonNull UrlProcessor urlProcessor, @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(); .build();
} }
@ -44,22 +48,28 @@ public class SpannableHtmlParser {
} }
public static Builder builderWithDefaults(@NonNull SpannableTheme theme) { public static Builder builderWithDefaults(@NonNull SpannableTheme theme) {
return builderWithDefaults(theme, null, null, null); return builderWithDefaults(theme, null, null, null,
null);
} }
public static Builder builderWithDefaults( public static Builder builderWithDefaults(
@NonNull SpannableTheme theme, @NonNull SpannableTheme theme,
@Nullable AsyncDrawable.Loader asyncDrawableLoader, @Nullable AsyncDrawable.Loader asyncDrawableLoader,
@Nullable UrlProcessor urlProcessor, @Nullable UrlProcessor urlProcessor,
@Nullable LinkSpan.Resolver resolver @Nullable LinkSpan.Resolver linkResolver,
@Nullable ImageClickResolver imageClickResolver
) { ) {
if (urlProcessor == null) { if (urlProcessor == null) {
urlProcessor = new UrlProcessorNoOp(); urlProcessor = new UrlProcessorNoOp();
} }
if (resolver == null) { if (linkResolver == null) {
resolver = new LinkResolverDef(); linkResolver = new LinkResolverDef();
}
if (imageClickResolver == null) {
imageClickResolver = new ImageClickResolverDef();
} }
final BoldProvider boldProvider = new BoldProvider(); final BoldProvider boldProvider = new BoldProvider();
@ -68,7 +78,8 @@ public class SpannableHtmlParser {
final ImageProvider imageProvider; final ImageProvider imageProvider;
if (asyncDrawableLoader != null) { if (asyncDrawableLoader != null) {
imageProvider = new ImageProviderImpl(theme, asyncDrawableLoader, urlProcessor); imageProvider = new ImageProviderImpl(theme, asyncDrawableLoader, urlProcessor,
imageClickResolver);
} else { } else {
imageProvider = null; imageProvider = null;
} }
@ -86,7 +97,7 @@ public class SpannableHtmlParser {
.simpleTag("del", strikeProvider) .simpleTag("del", strikeProvider)
.simpleTag("s", strikeProvider) .simpleTag("s", strikeProvider)
.simpleTag("strike", strikeProvider) .simpleTag("strike", strikeProvider)
.simpleTag("a", new LinkProvider(theme, urlProcessor, resolver)) .simpleTag("a", new LinkProvider(theme, urlProcessor, linkResolver))
.imageProvider(imageProvider); .imageProvider(imageProvider);
} }

View File

@ -7,14 +7,14 @@ import ru.noties.markwon.spans.SubScriptSpan;
class SubScriptProvider implements SpannableHtmlParser.SpanProvider { class SubScriptProvider implements SpannableHtmlParser.SpanProvider {
private final SpannableTheme theme; private final SpannableTheme mTheme;
public SubScriptProvider(SpannableTheme theme) { public SubScriptProvider(SpannableTheme theme) {
this.theme = theme; mTheme = theme;
} }
@Override @Override
public Object provide(@NonNull SpannableHtmlParser.Tag tag) { public Object provide(@NonNull SpannableHtmlParser.Tag tag) {
return new SubScriptSpan(theme); return new SubScriptSpan(mTheme);
} }
} }

View File

@ -7,14 +7,14 @@ import ru.noties.markwon.spans.SuperScriptSpan;
class SuperScriptProvider implements SpannableHtmlParser.SpanProvider { class SuperScriptProvider implements SpannableHtmlParser.SpanProvider {
private final SpannableTheme theme; private final SpannableTheme mTheme;
SuperScriptProvider(SpannableTheme theme) { SuperScriptProvider(SpannableTheme theme) {
this.theme = theme; mTheme = theme;
} }
@Override @Override
public Object provide(@NonNull SpannableHtmlParser.Tag tag) { public Object provide(@NonNull SpannableHtmlParser.Tag tag) {
return new SuperScriptSpan(theme); return new SuperScriptSpan(mTheme);
} }
} }

View File

@ -17,27 +17,27 @@ public class AsyncDrawable extends Drawable {
void cancel(@NonNull String destination); void cancel(@NonNull String destination);
} }
private final String destination; private final String mDestination;
private final Loader loader; private final Loader mLoader;
private Drawable result; private Drawable mResult;
private Callback callback; private Callback mCallback;
public AsyncDrawable(@NonNull String destination, @NonNull Loader loader) { public AsyncDrawable(@NonNull String destination, @NonNull Loader loader) {
this.destination = destination; mDestination = destination;
this.loader = loader; mLoader = loader;
} }
public String getDestination() { public String getDestination() {
return destination; return mDestination;
} }
public Drawable getResult() { public Drawable getResult() {
return result; return mResult;
} }
public boolean hasResult() { public boolean hasResult() {
return result != null; return mResult != null;
} }
public boolean isAttached() { public boolean isAttached() {
@ -47,39 +47,39 @@ public class AsyncDrawable extends Drawable {
// yeah // yeah
public void setCallback2(@Nullable Callback callback) { public void setCallback2(@Nullable Callback callback) {
this.callback = callback; mCallback = callback;
super.setCallback(callback); super.setCallback(callback);
// if not null -> means we are attached // if not null -> means we are attached
if (callback != null) { if (callback != null) {
loader.load(destination, this); mLoader.load(mDestination, this);
} else { } else {
if (result != null) { if (mResult != null) {
result.setCallback(null); mResult.setCallback(null);
// let's additionally stop if it Animatable // let's additionally stop if it Animatable
if (result instanceof Animatable) { if (mResult instanceof Animatable) {
((Animatable) result).stop(); ((Animatable) mResult).stop();
} }
} }
loader.cancel(destination); mLoader.cancel(mDestination);
} }
} }
public void setResult(@NonNull Drawable result) { public void setResult(@NonNull Drawable result) {
// if we have previous one, detach it // if we have previous one, detach it
if (this.result != null) { if (mResult != null) {
this.result.setCallback(null); mResult.setCallback(null);
} }
this.result = result; mResult = result;
this.result.setCallback(callback); mResult.setCallback(mCallback);
// should we copy the data here? like bounds etc? // should we copy the data here? like bounds etc?
// if we are async and we load some image from some source // 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()); setBounds(result.getBounds());
invalidateSelf(); invalidateSelf();
} }
@ -87,7 +87,7 @@ public class AsyncDrawable extends Drawable {
@Override @Override
public void draw(@NonNull Canvas canvas) { public void draw(@NonNull Canvas canvas) {
if (hasResult()) { if (hasResult()) {
result.draw(canvas); mResult.draw(canvas);
} }
} }
@ -105,7 +105,7 @@ public class AsyncDrawable extends Drawable {
public int getOpacity() { public int getOpacity() {
final int opacity; final int opacity;
if (hasResult()) { if (hasResult()) {
opacity = result.getOpacity(); opacity = mResult.getOpacity();
} else { } else {
opacity = PixelFormat.TRANSPARENT; opacity = PixelFormat.TRANSPARENT;
} }
@ -116,7 +116,7 @@ public class AsyncDrawable extends Drawable {
public int getIntrinsicWidth() { public int getIntrinsicWidth() {
final int out; final int out;
if (hasResult()) { if (hasResult()) {
out = result.getIntrinsicWidth(); out = mResult.getIntrinsicWidth();
} else { } else {
out = 0; out = 0;
} }
@ -127,7 +127,7 @@ public class AsyncDrawable extends Drawable {
public int getIntrinsicHeight() { public int getIntrinsicHeight() {
final int out; final int out;
if (hasResult()) { if (hasResult()) {
out = result.getIntrinsicHeight(); out = mResult.getIntrinsicHeight();
} else { } else {
out = 0; out = 0;
} }

View File

@ -22,15 +22,15 @@ public class AsyncDrawableSpan extends ReplacementSpan {
public static final int ALIGN_BOTTOM = 0; public static final int ALIGN_BOTTOM = 0;
public static final int ALIGN_BASELINE = 1; public static final int ALIGN_BASELINE = 1;
public static final int ALIGN_CENTER = 2; // will only center if drawable height is less than text line height public static final int ALIGN_CENTER = 2; // will only center if drawable height is less than mText line height
private final SpannableTheme theme; private final SpannableTheme mTheme;
private final AsyncDrawable drawable; private final AsyncDrawable mDrawable;
private final int alignment; private final int mAlignment;
private final boolean replacementTextIsLink; private final boolean mReplacementTextIsLink;
private int lastKnownDrawX; private int mLastKnownDrawX;
private int lastKnownDrawY; private int mLastKnownDrawY;
public AsyncDrawableSpan(@NonNull SpannableTheme theme, @NonNull AsyncDrawable drawable) { public AsyncDrawableSpan(@NonNull SpannableTheme theme, @NonNull AsyncDrawable drawable) {
this(theme, drawable, ALIGN_BOTTOM); this(theme, drawable, ALIGN_BOTTOM);
@ -48,10 +48,10 @@ public class AsyncDrawableSpan extends ReplacementSpan {
@NonNull AsyncDrawable drawable, @NonNull AsyncDrawable drawable,
@Alignment int alignment, @Alignment int alignment,
boolean replacementTextIsLink) { boolean replacementTextIsLink) {
this.theme = theme; mTheme = theme;
this.drawable = drawable; mDrawable = drawable;
this.alignment = alignment; mAlignment = alignment;
this.replacementTextIsLink = replacementTextIsLink; mReplacementTextIsLink = replacementTextIsLink;
// additionally set intrinsic bounds if empty // additionally set intrinsic bounds if empty
final Rect rect = drawable.getBounds(); final Rect rect = drawable.getBounds();
@ -68,13 +68,13 @@ public class AsyncDrawableSpan extends ReplacementSpan {
@IntRange(from = 0) int end, @IntRange(from = 0) int end,
@Nullable Paint.FontMetricsInt fm) { @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; final int size;
if (drawable.hasResult()) { if (mDrawable.hasResult()) {
final Rect rect = drawable.getBounds(); final Rect rect = mDrawable.getBounds();
if (fm != null) { if (fm != null) {
fm.ascent = -rect.bottom; fm.ascent = -rect.bottom;
@ -89,11 +89,11 @@ public class AsyncDrawableSpan extends ReplacementSpan {
} else { } else {
// we will apply style here in case if theme modifies textSize or style (affects metrics) // we will apply style here in case if theme modifies textSize or style (affects metrics)
if (replacementTextIsLink) { if (mReplacementTextIsLink) {
theme.applyLinkStyle(paint); 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); size = (int) (paint.measureText(text, start, end) + .5F);
} }
@ -112,10 +112,10 @@ public class AsyncDrawableSpan extends ReplacementSpan {
int bottom, int bottom,
@NonNull Paint paint) { @NonNull Paint paint) {
this.lastKnownDrawX = (int) (x + .5F); mLastKnownDrawX = (int) (x + .5F);
this.lastKnownDrawY = y; mLastKnownDrawY = y;
final AsyncDrawable drawable = this.drawable; final AsyncDrawable drawable = mDrawable;
if (drawable.hasResult()) { if (drawable.hasResult()) {
@ -124,9 +124,9 @@ public class AsyncDrawableSpan extends ReplacementSpan {
final int save = canvas.save(); final int save = canvas.save();
try { try {
final int translationY; final int translationY;
if (ALIGN_CENTER == alignment) { if (ALIGN_CENTER == mAlignment) {
translationY = b - ((bottom - top - drawable.getBounds().height()) / 2); translationY = b - ((bottom - top - drawable.getBounds().height()) / 2);
} else if (ALIGN_BASELINE == alignment) { } else if (ALIGN_BASELINE == mAlignment) {
translationY = b - paint.getFontMetricsInt().descent; translationY = b - paint.getFontMetricsInt().descent;
} else { } else {
translationY = b; translationY = b;
@ -142,24 +142,24 @@ public class AsyncDrawableSpan extends ReplacementSpan {
// let's focus on main functionality and then think of it // let's focus on main functionality and then think of it
final float textY = CanvasUtils.textCenterY(top, bottom, paint); final float textY = CanvasUtils.textCenterY(top, bottom, paint);
if (replacementTextIsLink) { if (mReplacementTextIsLink) {
theme.applyLinkStyle(paint); 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); canvas.drawText(text, start, end, x, textY, paint);
} }
} }
public AsyncDrawable getDrawable() { public AsyncDrawable getDrawable() {
return drawable; return mDrawable;
} }
public int lastKnownDrawX() { public int lastKnownDrawX() {
return lastKnownDrawX; return mLastKnownDrawX;
} }
public int lastKnownDrawY() { public int lastKnownDrawY() {
return lastKnownDrawY; return mLastKnownDrawY;
} }
} }

View File

@ -9,19 +9,19 @@ import android.text.style.LeadingMarginSpan;
public class BlockQuoteSpan implements LeadingMarginSpan { public class BlockQuoteSpan implements LeadingMarginSpan {
private final SpannableTheme theme; private final SpannableTheme mTheme;
private final Rect rect = ObjectsPool.rect(); private final Rect mRect = ObjectsPool.rect();
private final Paint paint = ObjectsPool.paint(); private final Paint mPaint = ObjectsPool.paint();
private final int indent; private final int mIndent;
public BlockQuoteSpan(@NonNull SpannableTheme theme, int indent) { public BlockQuoteSpan(@NonNull SpannableTheme theme, int indent) {
this.theme = theme; mTheme = theme;
this.indent = indent; mIndent = indent;
} }
@Override @Override
public int getLeadingMargin(boolean first) { public int getLeadingMargin(boolean first) {
return theme.getBlockMargin(); return mTheme.getBlockMargin();
} }
@Override @Override
@ -39,13 +39,13 @@ public class BlockQuoteSpan implements LeadingMarginSpan {
boolean first, boolean first,
Layout layout) { 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); final int left = mTheme.getBlockMargin() * (mIndent - 1);
rect.set(left, top, left + width, bottom); mRect.set(left, top, left + width, bottom);
c.drawRect(rect, paint); c.drawRect(mRect, mPaint);
} }
} }

View File

@ -11,27 +11,27 @@ import android.text.style.LeadingMarginSpan;
public class BulletListItemSpan implements LeadingMarginSpan { public class BulletListItemSpan implements LeadingMarginSpan {
private SpannableTheme theme; private SpannableTheme mTheme;
private final Paint paint = ObjectsPool.paint(); private final Paint mPaint = ObjectsPool.paint();
private final RectF circle = ObjectsPool.rectF(); private final RectF mCircle = ObjectsPool.rectF();
private final Rect rectangle = ObjectsPool.rect(); private final Rect mRectangle = ObjectsPool.rect();
private final int blockIndent; private final int mBlockIndent;
private final int level; private final int mLevel;
public BulletListItemSpan( public BulletListItemSpan(
@NonNull SpannableTheme theme, @NonNull SpannableTheme theme,
@IntRange(from = 0) int blockIndent, @IntRange(from = 0) int blockIndent,
@IntRange(from = 0) int level) { @IntRange(from = 0) int level) {
this.theme = theme; mTheme = theme;
this.blockIndent = blockIndent; mBlockIndent = blockIndent;
this.level = level; mLevel = level;
} }
@Override @Override
public int getLeadingMargin(boolean first) { public int getLeadingMargin(boolean first) {
return theme.getBlockMargin(); return mTheme.getBlockMargin();
} }
@Override @Override
@ -42,44 +42,44 @@ public class BulletListItemSpan implements LeadingMarginSpan {
return; return;
} }
paint.set(p); mPaint.set(p);
theme.applyListItemStyle(paint); mTheme.applyListItemStyle(mPaint);
final int save = c.save(); final int save = c.save();
try { try {
final int width = theme.getBlockMargin(); final int width = mTheme.getBlockMargin();
final int height = bottom - top; 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 marginLeft = (width - side) / 2;
final int marginTop = (height - 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 t = top + marginTop;
final int r = l + side; final int r = l + side;
final int b = t + side; final int b = t + side;
if (level == 0 if (mLevel == 0
|| level == 1) { || 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.FILL
: Paint.Style.STROKE; : Paint.Style.STROKE;
paint.setStyle(style); mPaint.setStyle(style);
c.drawOval(circle, paint); c.drawOval(mCircle, mPaint);
} else { } 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 { } finally {

View File

@ -11,15 +11,15 @@ import android.text.style.MetricAffectingSpan;
public class CodeSpan extends MetricAffectingSpan implements LeadingMarginSpan { public class CodeSpan extends MetricAffectingSpan implements LeadingMarginSpan {
private final SpannableTheme theme; private final SpannableTheme mTheme;
private final Rect rect = ObjectsPool.rect(); private final Rect mRect = ObjectsPool.rect();
private final Paint paint = ObjectsPool.paint(); private final Paint mPaint = ObjectsPool.paint();
private final boolean multiline; private final boolean mMultiline;
public CodeSpan(@NonNull SpannableTheme theme, boolean multiline) { public CodeSpan(@NonNull SpannableTheme theme, boolean multiline) {
this.theme = theme; mTheme = theme;
this.multiline = multiline; mMultiline = multiline;
} }
@Override @Override
@ -30,31 +30,31 @@ public class CodeSpan extends MetricAffectingSpan implements LeadingMarginSpan {
@Override @Override
public void updateDrawState(TextPaint ds) { public void updateDrawState(TextPaint ds) {
apply(ds); apply(ds);
if (!multiline) { if (!mMultiline) {
ds.bgColor = theme.getCodeBackgroundColor(ds); ds.bgColor = mTheme.getCodeBackgroundColor(ds);
} }
} }
private void apply(TextPaint p) { private void apply(TextPaint p) {
theme.applyCodeTextStyle(p); mTheme.applyCodeTextStyle(p);
} }
@Override @Override
public int getLeadingMargin(boolean first) { public int getLeadingMargin(boolean first) {
return multiline ? theme.getCodeMultilineMargin() : 0; return mMultiline ? mTheme.getCodeMultilineMargin() : 0;
} }
@Override @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) { 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); mPaint.setStyle(Paint.Style.FILL);
paint.setColor(theme.getCodeBackgroundColor(p)); 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);
} }
} }
} }

View File

@ -12,16 +12,16 @@ import android.text.style.MetricAffectingSpan;
public class HeadingSpan extends MetricAffectingSpan implements LeadingMarginSpan { public class HeadingSpan extends MetricAffectingSpan implements LeadingMarginSpan {
private final SpannableTheme theme; private final SpannableTheme mTheme;
private final Rect rect = ObjectsPool.rect(); private final Rect mRect = ObjectsPool.rect();
private final Paint paint = ObjectsPool.paint(); private final Paint mPaint = ObjectsPool.paint();
private final int level; private final int mLevel;
private final int textLength; private final int mTextLength;
public HeadingSpan(@NonNull SpannableTheme theme, @IntRange(from = 1, to = 6) int level, @IntRange(from = 0) int textLength) { public HeadingSpan(@NonNull SpannableTheme theme, @IntRange(from = 1, to = 6) int level, @IntRange(from = 0) int textLength) {
this.theme = theme; mTheme = theme;
this.level = level; mLevel = level;
this.textLength = textLength; mTextLength = textLength;
} }
@Override @Override
@ -35,7 +35,7 @@ public class HeadingSpan extends MetricAffectingSpan implements LeadingMarginSpa
} }
private void apply(TextPaint paint) { private void apply(TextPaint paint) {
theme.applyHeadingTextStyle(paint, level); mTheme.applyHeadingTextStyle(paint, mLevel);
} }
@Override @Override
@ -47,19 +47,19 @@ public class HeadingSpan extends MetricAffectingSpan implements LeadingMarginSpa
@Override @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) { 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 if (mLevel == 1
|| level == 2) { || mLevel == 2) {
if ((start + textLength) == end) { if ((start + mTextLength) == end) {
paint.set(p); 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); final int b = (int) (bottom - height + .5F);
rect.set(x, b, c.getWidth(), bottom); mRect.set(x, b, c.getWidth(), bottom);
c.drawRect(rect, paint); c.drawRect(mRect, mPaint);
} }
} }
} }

View File

@ -11,23 +11,23 @@ public class LinkSpan extends ClickableSpan {
void resolve(View view, @NonNull String link); void resolve(View view, @NonNull String link);
} }
private final SpannableTheme theme; private final SpannableTheme mTheme;
private final String link; private final String mLink;
private final Resolver resolver; private final Resolver mResolver;
public LinkSpan(@NonNull SpannableTheme theme, @NonNull String link, @NonNull Resolver resolver) { public LinkSpan(@NonNull SpannableTheme theme, @NonNull String link, @NonNull Resolver resolver) {
this.theme = theme; mTheme = theme;
this.link = link; mLink = link;
this.resolver = resolver; mResolver = resolver;
} }
@Override @Override
public void onClick(View widget) { public void onClick(View widget) {
resolver.resolve(widget, link); mResolver.resolve(widget, mLink);
} }
@Override @Override
public void updateDrawState(TextPaint ds) { public void updateDrawState(TextPaint ds) {
theme.applyLinkStyle(ds); mTheme.applyLinkStyle(ds);
} }
} }

View File

@ -9,23 +9,23 @@ import android.text.style.LeadingMarginSpan;
public class OrderedListItemSpan implements LeadingMarginSpan { public class OrderedListItemSpan implements LeadingMarginSpan {
private final SpannableTheme theme; private final SpannableTheme mTheme;
private final String number; private final String mNumber;
private final int blockIndent; private final int mBlockIndent;
public OrderedListItemSpan( public OrderedListItemSpan(
@NonNull SpannableTheme theme, @NonNull SpannableTheme theme,
@NonNull String number, @NonNull String number,
@IntRange(from = 0) int blockIndent @IntRange(from = 0) int blockIndent
) { ) {
this.theme = theme; mTheme = theme;
this.number = number; mNumber = number;
this.blockIndent = blockIndent; mBlockIndent = blockIndent;
} }
@Override @Override
public int getLeadingMargin(boolean first) { public int getLeadingMargin(boolean first) {
return theme.getBlockMargin(); return mTheme.getBlockMargin();
} }
@Override @Override
@ -36,14 +36,14 @@ public class OrderedListItemSpan implements LeadingMarginSpan {
return; return;
} }
theme.applyListItemStyle(p); mTheme.applyListItemStyle(p);
final int width = theme.getBlockMargin(); final int width = mTheme.getBlockMargin();
final int numberWidth = (int) (p.measureText(number) + .5F); final int numberWidth = (int) (p.measureText(mNumber) + .5F);
final int numberX = (width * blockIndent) - numberWidth; final int numberX = (width * mBlockIndent) - numberWidth;
final float numberY = CanvasUtils.textCenterY(top, bottom, p); final float numberY = CanvasUtils.textCenterY(top, bottom, p);
c.drawText(number, numberX, numberY, p); c.drawText(mNumber, numberX, numberY, p);
} }
} }

View File

@ -122,128 +122,128 @@ public class SpannableTheme {
protected static final int TABLE_ODD_ROW_DEF_ALPHA = 22; protected static final int TABLE_ODD_ROW_DEF_ALPHA = 22;
protected final int linkColor; protected final int mLinkColor;
// used in quote, lists // used in quote, lists
protected final int blockMargin; protected final int mBlockMargin;
// by default it's 1/4th of `blockMargin` // by default it's 1/4th of `mBlockMargin`
protected final int blockQuoteWidth; protected final int mBlockQuoteWidth;
// by default it's text color with `BLOCK_QUOTE_DEF_COLOR_ALPHA` applied alpha // by default it's mText color with `BLOCK_QUOTE_DEF_COLOR_ALPHA` applied alpha
protected final int blockQuoteColor; protected final int mBlockQuoteColor;
// by default uses text color (applied for un-ordered lists & ordered (bullets & numbers) // by default uses mText color (applied for un-ordered lists & ordered (bullets & numbers)
protected final int listItemColor; protected final int mListItemColor;
// by default the stroke color of a paint object // 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 // width of bullet, by default min(mBlockMargin, height) / 2
protected final int bulletWidth; protected final int mBulletWidth;
// by default - main text color // by default - main mText color
protected final int codeTextColor; protected final int mCodeTextColor;
// by default 0.1 alpha of textColor/codeTextColor // by default 0.1 alpha of textColor/mCodeTextColor
protected final int codeBackgroundColor; 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` // 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) // 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 // 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 // applied ONLY if default typeface was used, otherwise, not applied
protected final int codeTextSize; protected final int mCodeTextSize;
// by default paint.getStrokeWidth // by default paint.getStrokeWidth
protected final int headingBreakHeight; protected final int mHeadingBreakHeight;
// by default, text color with `HEADING_DEF_BREAK_COLOR_ALPHA` applied alpha // by default, mText color with `HEADING_DEF_BREAK_COLOR_ALPHA` applied alpha
protected final int headingBreakColor; protected final int mHeadingBreakColor;
// by default `SCRIPT_DEF_TEXT_SIZE_RATIO` // 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 // by default textColor with `THEMATIC_BREAK_DEF_ALPHA` applied alpha
protected final int thematicBreakColor; protected final int mThematicBreakColor;
// by default paint.strokeWidth // by default paint.strokeWidth
protected final int thematicBreakHeight; protected final int mThematicBreakHeight;
// by default 0 // by default 0
protected final int tableCellPadding; protected final int mTableCellPadding;
// by default paint.color * TABLE_BORDER_DEF_ALPHA // 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 // 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) // drawable that will be used to render checkbox (should be stateful)
// TaskListDrawable can be used // TaskListDrawable can be used
protected final Drawable taskListDrawable; protected final Drawable mTaskListDrawable;
protected SpannableTheme(@NonNull Builder builder) { protected SpannableTheme(@NonNull Builder builder) {
this.linkColor = builder.linkColor; mLinkColor = builder.linkColor;
this.blockMargin = builder.blockMargin; mBlockMargin = builder.blockMargin;
this.blockQuoteWidth = builder.blockQuoteWidth; mBlockQuoteWidth = builder.blockQuoteWidth;
this.blockQuoteColor = builder.blockQuoteColor; mBlockQuoteColor = builder.blockQuoteColor;
this.listItemColor = builder.listItemColor; mListItemColor = builder.listItemColor;
this.bulletListItemStrokeWidth = builder.bulletListItemStrokeWidth; mBulletListItemStrokeWidth = builder.bulletListItemStrokeWidth;
this.bulletWidth = builder.bulletWidth; mBulletWidth = builder.bulletWidth;
this.codeTextColor = builder.codeTextColor; mCodeTextColor = builder.codeTextColor;
this.codeBackgroundColor = builder.codeBackgroundColor; mCodeBackgroundColor = builder.codeBackgroundColor;
this.codeMultilineMargin = builder.codeMultilineMargin; mCodeMultilineMargin = builder.codeMultilineMargin;
this.codeTypeface = builder.codeTypeface; mCodeTypeface = builder.codeTypeface;
this.codeTextSize = builder.codeTextSize; mCodeTextSize = builder.codeTextSize;
this.headingBreakHeight = builder.headingBreakHeight; mHeadingBreakHeight = builder.headingBreakHeight;
this.headingBreakColor = builder.headingBreakColor; mHeadingBreakColor = builder.headingBreakColor;
this.scriptTextSizeRatio = builder.scriptTextSizeRatio; mScriptTextSizeRatio = builder.scriptTextSizeRatio;
this.thematicBreakColor = builder.thematicBreakColor; mThematicBreakColor = builder.thematicBreakColor;
this.thematicBreakHeight = builder.thematicBreakHeight; mThematicBreakHeight = builder.thematicBreakHeight;
this.tableCellPadding = builder.tableCellPadding; mTableCellPadding = builder.tableCellPadding;
this.tableBorderColor = builder.tableBorderColor; mTableBorderColor = builder.tableBorderColor;
this.tableBorderWidth = builder.tableBorderWidth; mTableBorderWidth = builder.tableBorderWidth;
this.tableOddRowBackgroundColor = builder.tableOddRowBackgroundColor; mTableOddRowBackgroundColor = builder.tableOddRowBackgroundColor;
this.taskListDrawable = builder.taskListDrawable; mTaskListDrawable = builder.taskListDrawable;
} }
public void applyLinkStyle(@NonNull Paint paint) { public void applyLinkStyle(@NonNull Paint paint) {
paint.setUnderlineText(true); paint.setUnderlineText(true);
if (linkColor != 0) { if (mLinkColor != 0) {
// by default we will be using text color // by default we will be using mText color
paint.setColor(linkColor); paint.setColor(mLinkColor);
} }
} }
public void applyBlockQuoteStyle(@NonNull Paint paint) { public void applyBlockQuoteStyle(@NonNull Paint paint) {
final int color; final int color;
if (blockQuoteColor == 0) { if (mBlockQuoteColor == 0) {
color = ColorUtils.applyAlpha(paint.getColor(), BLOCK_QUOTE_DEF_COLOR_ALPHA); color = ColorUtils.applyAlpha(paint.getColor(), BLOCK_QUOTE_DEF_COLOR_ALPHA);
} else { } else {
color = blockQuoteColor; color = mBlockQuoteColor;
} }
paint.setStyle(Paint.Style.FILL); paint.setStyle(Paint.Style.FILL);
paint.setColor(color); paint.setColor(color);
} }
public int getBlockMargin() { public int getBlockMargin() {
return blockMargin; return mBlockMargin;
} }
public int getBlockQuoteWidth() { public int getBlockQuoteWidth() {
final int out; final int out;
if (blockQuoteWidth == 0) { if (mBlockQuoteWidth == 0) {
out = (int) (blockMargin * .25F + .5F); out = (int) (mBlockMargin * .25F + .5F);
} else { } else {
out = blockQuoteWidth; out = mBlockQuoteWidth;
} }
return out; return out;
} }
@ -251,28 +251,28 @@ public class SpannableTheme {
public void applyListItemStyle(@NonNull Paint paint) { public void applyListItemStyle(@NonNull Paint paint) {
final int color; final int color;
if (listItemColor != 0) { if (mListItemColor != 0) {
color = listItemColor; color = mListItemColor;
} else { } else {
color = paint.getColor(); color = paint.getColor();
} }
paint.setColor(color); paint.setColor(color);
if (bulletListItemStrokeWidth != 0) { if (mBulletListItemStrokeWidth != 0) {
paint.setStrokeWidth(bulletListItemStrokeWidth); paint.setStrokeWidth(mBulletListItemStrokeWidth);
} }
} }
public int getBulletWidth(int height) { public int getBulletWidth(int height) {
final int min = Math.min(blockMargin, height) / 2; final int min = Math.min(mBlockMargin, height) / 2;
final int width; final int width;
if (bulletWidth == 0 if (mBulletWidth == 0
|| bulletWidth > min) { || mBulletWidth > min) {
width = min; width = min;
} else { } else {
width = bulletWidth; width = mBulletWidth;
} }
return width; return width;
@ -280,27 +280,27 @@ public class SpannableTheme {
public void applyCodeTextStyle(@NonNull Paint paint) { public void applyCodeTextStyle(@NonNull Paint paint) {
if (codeTextColor != 0) { if (mCodeTextColor != 0) {
paint.setColor(codeTextColor); paint.setColor(mCodeTextColor);
} }
// custom typeface was set // 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 // please note that we won't be calculating textSize
// (like we do when no Typeface is provided), if it's some specific typeface // (like we do when no Typeface is provided), if it's some specific typeface
// we would confuse users about textSize // we would confuse users about textSize
if (codeTextSize != 0) { if (mCodeTextSize != 0) {
paint.setTextSize(codeTextSize); paint.setTextSize(mCodeTextSize);
} }
} else { } else {
paint.setTypeface(Typeface.MONOSPACE); paint.setTypeface(Typeface.MONOSPACE);
final float textSize; final float textSize;
if (codeTextSize != 0) { if (mCodeTextSize != 0) {
textSize = codeTextSize; textSize = mCodeTextSize;
} else { } else {
textSize = paint.getTextSize() * CODE_DEF_TEXT_SIZE_RATIO; textSize = paint.getTextSize() * CODE_DEF_TEXT_SIZE_RATIO;
} }
@ -309,13 +309,13 @@ public class SpannableTheme {
} }
public int getCodeMultilineMargin() { public int getCodeMultilineMargin() {
return codeMultilineMargin; return mCodeMultilineMargin;
} }
public int getCodeBackgroundColor(@NonNull Paint paint) { public int getCodeBackgroundColor(@NonNull Paint paint) {
final int color; final int color;
if (codeBackgroundColor != 0) { if (mCodeBackgroundColor != 0) {
color = codeBackgroundColor; color = mCodeBackgroundColor;
} else { } else {
color = ColorUtils.applyAlpha(paint.getColor(), CODE_DEF_BACKGROUND_COLOR_ALPHA); color = ColorUtils.applyAlpha(paint.getColor(), CODE_DEF_BACKGROUND_COLOR_ALPHA);
} }
@ -329,25 +329,25 @@ public class SpannableTheme {
public void applyHeadingBreakStyle(@NonNull Paint paint) { public void applyHeadingBreakStyle(@NonNull Paint paint) {
final int color; final int color;
if (headingBreakColor != 0) { if (mHeadingBreakColor != 0) {
color = headingBreakColor; color = mHeadingBreakColor;
} else { } else {
color = ColorUtils.applyAlpha(paint.getColor(), HEADING_DEF_BREAK_COLOR_ALPHA); color = ColorUtils.applyAlpha(paint.getColor(), HEADING_DEF_BREAK_COLOR_ALPHA);
} }
paint.setColor(color); paint.setColor(color);
paint.setStyle(Paint.Style.FILL); paint.setStyle(Paint.Style.FILL);
if (headingBreakHeight != 0) { if (mHeadingBreakHeight != 0) {
//noinspection SuspiciousNameCombination //noinspection SuspiciousNameCombination
paint.setStrokeWidth(headingBreakHeight); paint.setStrokeWidth(mHeadingBreakHeight);
} }
} }
public void applySuperScriptStyle(@NonNull TextPaint paint) { public void applySuperScriptStyle(@NonNull TextPaint paint) {
final float ratio; final float ratio;
if (Float.compare(scriptTextSizeRatio, .0F) == 0) { if (Float.compare(mScriptTextSizeRatio, .0F) == 0) {
ratio = SCRIPT_DEF_TEXT_SIZE_RATIO; ratio = SCRIPT_DEF_TEXT_SIZE_RATIO;
} else { } else {
ratio = scriptTextSizeRatio; ratio = mScriptTextSizeRatio;
} }
paint.setTextSize(paint.getTextSize() * ratio); paint.setTextSize(paint.getTextSize() * ratio);
paint.baselineShift += (int) (paint.ascent() / 2); paint.baselineShift += (int) (paint.ascent() / 2);
@ -355,10 +355,10 @@ public class SpannableTheme {
public void applySubScriptStyle(@NonNull TextPaint paint) { public void applySubScriptStyle(@NonNull TextPaint paint) {
final float ratio; final float ratio;
if (Float.compare(scriptTextSizeRatio, .0F) == 0) { if (Float.compare(mScriptTextSizeRatio, .0F) == 0) {
ratio = SCRIPT_DEF_TEXT_SIZE_RATIO; ratio = SCRIPT_DEF_TEXT_SIZE_RATIO;
} else { } else {
ratio = scriptTextSizeRatio; ratio = mScriptTextSizeRatio;
} }
paint.setTextSize(paint.getTextSize() * ratio); paint.setTextSize(paint.getTextSize() * ratio);
paint.baselineShift -= (int) (paint.ascent() / 2); paint.baselineShift -= (int) (paint.ascent() / 2);
@ -366,35 +366,35 @@ public class SpannableTheme {
public void applyThematicBreakStyle(@NonNull Paint paint) { public void applyThematicBreakStyle(@NonNull Paint paint) {
final int color; final int color;
if (thematicBreakColor != 0) { if (mThematicBreakColor != 0) {
color = thematicBreakColor; color = mThematicBreakColor;
} else { } else {
color = ColorUtils.applyAlpha(paint.getColor(), THEMATIC_BREAK_DEF_ALPHA); color = ColorUtils.applyAlpha(paint.getColor(), THEMATIC_BREAK_DEF_ALPHA);
} }
paint.setColor(color); paint.setColor(color);
paint.setStyle(Paint.Style.FILL); paint.setStyle(Paint.Style.FILL);
if (thematicBreakHeight != 0) { if (mThematicBreakHeight != 0) {
//noinspection SuspiciousNameCombination //noinspection SuspiciousNameCombination
paint.setStrokeWidth(thematicBreakHeight); paint.setStrokeWidth(mThematicBreakHeight);
} }
} }
public int tableCellPadding() { public int tableCellPadding() {
return tableCellPadding; return mTableCellPadding;
} }
public void applyTableBorderStyle(@NonNull Paint paint) { public void applyTableBorderStyle(@NonNull Paint paint) {
final int color; final int color;
if (tableBorderColor == 0) { if (mTableBorderColor == 0) {
color = ColorUtils.applyAlpha(paint.getColor(), TABLE_BORDER_DEF_ALPHA); color = ColorUtils.applyAlpha(paint.getColor(), TABLE_BORDER_DEF_ALPHA);
} else { } else {
color = tableBorderColor; color = mTableBorderColor;
} }
if (tableBorderWidth != 0) { if (mTableBorderWidth != 0) {
paint.setStrokeWidth(tableBorderWidth); paint.setStrokeWidth(mTableBorderWidth);
} }
paint.setColor(color); paint.setColor(color);
@ -403,10 +403,10 @@ public class SpannableTheme {
public void applyTableOddRowStyle(@NonNull Paint paint) { public void applyTableOddRowStyle(@NonNull Paint paint) {
final int color; final int color;
if (tableOddRowBackgroundColor == 0) { if (mTableOddRowBackgroundColor == 0) {
color = ColorUtils.applyAlpha(paint.getColor(), TABLE_ODD_ROW_DEF_ALPHA); color = ColorUtils.applyAlpha(paint.getColor(), TABLE_ODD_ROW_DEF_ALPHA);
} else { } else {
color = tableOddRowBackgroundColor; color = mTableOddRowBackgroundColor;
} }
paint.setColor(color); paint.setColor(color);
paint.setStyle(Paint.Style.FILL); paint.setStyle(Paint.Style.FILL);
@ -418,7 +418,7 @@ public class SpannableTheme {
*/ */
@Nullable @Nullable
public Drawable getTaskListDrawable() { public Drawable getTaskListDrawable() {
return taskListDrawable; return mTaskListDrawable;
} }
public static class Builder { public static class Builder {
@ -450,28 +450,28 @@ public class SpannableTheme {
} }
Builder(@NonNull SpannableTheme theme) { Builder(@NonNull SpannableTheme theme) {
this.linkColor = theme.linkColor; linkColor = theme.mLinkColor;
this.blockMargin = theme.blockMargin; blockMargin = theme.mBlockMargin;
this.blockQuoteWidth = theme.blockQuoteWidth; blockQuoteWidth = theme.mBlockQuoteWidth;
this.blockQuoteColor = theme.blockQuoteColor; blockQuoteColor = theme.mBlockQuoteColor;
this.listItemColor = theme.listItemColor; listItemColor = theme.mListItemColor;
this.bulletListItemStrokeWidth = theme.bulletListItemStrokeWidth; bulletListItemStrokeWidth = theme.mBulletListItemStrokeWidth;
this.bulletWidth = theme.bulletWidth; bulletWidth = theme.mBulletWidth;
this.codeTextColor = theme.codeTextColor; codeTextColor = theme.mCodeTextColor;
this.codeBackgroundColor = theme.codeBackgroundColor; codeBackgroundColor = theme.mCodeBackgroundColor;
this.codeMultilineMargin = theme.codeMultilineMargin; codeMultilineMargin = theme.mCodeMultilineMargin;
this.codeTypeface = theme.codeTypeface; codeTypeface = theme.mCodeTypeface;
this.codeTextSize = theme.codeTextSize; codeTextSize = theme.mCodeTextSize;
this.headingBreakHeight = theme.headingBreakHeight; headingBreakHeight = theme.mHeadingBreakHeight;
this.headingBreakColor = theme.headingBreakColor; headingBreakColor = theme.mHeadingBreakColor;
this.scriptTextSizeRatio = theme.scriptTextSizeRatio; scriptTextSizeRatio = theme.mScriptTextSizeRatio;
this.thematicBreakColor = theme.thematicBreakColor; thematicBreakColor = theme.mThematicBreakColor;
this.thematicBreakHeight = theme.thematicBreakHeight; thematicBreakHeight = theme.mThematicBreakHeight;
this.tableCellPadding = theme.tableCellPadding; tableCellPadding = theme.mTableCellPadding;
this.tableBorderColor = theme.tableBorderColor; tableBorderColor = theme.mTableBorderColor;
this.tableBorderWidth = theme.tableBorderWidth; tableBorderWidth = theme.mTableBorderWidth;
this.tableOddRowBackgroundColor = theme.tableOddRowBackgroundColor; tableOddRowBackgroundColor = theme.mTableOddRowBackgroundColor;
this.taskListDrawable = theme.taskListDrawable; taskListDrawable = theme.mTaskListDrawable;
} }
@NonNull @NonNull
@ -624,14 +624,14 @@ public class SpannableTheme {
private static class Dip { private static class Dip {
private final float density; private final float mDensity;
Dip(@NonNull Context context) { Dip(@NonNull Context context) {
this.density = context.getResources().getDisplayMetrics().density; mDensity = context.getResources().getDisplayMetrics().density;
} }
int toPx(int dp) { int toPx(int dp) {
return (int) (dp * density + .5F); return (int) (dp * mDensity + .5F);
} }
} }
} }

View File

@ -6,10 +6,10 @@ import android.text.style.MetricAffectingSpan;
public class SubScriptSpan extends MetricAffectingSpan { public class SubScriptSpan extends MetricAffectingSpan {
private final SpannableTheme theme; private final SpannableTheme mTheme;
public SubScriptSpan(@NonNull SpannableTheme theme) { public SubScriptSpan(@NonNull SpannableTheme theme) {
this.theme = theme; mTheme = theme;
} }
@Override @Override
@ -23,6 +23,6 @@ public class SubScriptSpan extends MetricAffectingSpan {
} }
private void apply(TextPaint paint) { private void apply(TextPaint paint) {
theme.applySubScriptStyle(paint); mTheme.applySubScriptStyle(paint);
} }
} }

View File

@ -6,10 +6,10 @@ import android.text.style.MetricAffectingSpan;
public class SuperScriptSpan extends MetricAffectingSpan { public class SuperScriptSpan extends MetricAffectingSpan {
private final SpannableTheme theme; private final SpannableTheme mTheme;
public SuperScriptSpan(@NonNull SpannableTheme theme) { public SuperScriptSpan(@NonNull SpannableTheme theme) {
this.theme = theme; mTheme = theme;
} }
@Override @Override
@ -23,6 +23,6 @@ public class SuperScriptSpan extends MetricAffectingSpan {
} }
private void apply(TextPaint paint) { private void apply(TextPaint paint) {
theme.applySuperScriptStyle(paint); mTheme.applySuperScriptStyle(paint);
} }
} }

View File

@ -35,57 +35,57 @@ public class TableRowSpan extends ReplacementSpan {
public static class Cell { public static class Cell {
final int alignment; final int mAlignment;
final CharSequence text; final CharSequence mText;
public Cell(@Alignment int alignment, CharSequence text) { public Cell(@Alignment int alignment, CharSequence text) {
this.alignment = alignment; mAlignment = alignment;
this.text = text; mText = text;
} }
@Alignment @Alignment
public int alignment() { public int alignment() {
return alignment; return mAlignment;
} }
public CharSequence text() { public CharSequence text() {
return text; return mText;
} }
@Override @Override
public String toString() { public String toString() {
return "Cell{" + return "Cell{" +
"alignment=" + alignment + "mAlignment=" + mAlignment +
", text=" + text + ", mText=" + mText +
'}'; '}';
} }
} }
private final SpannableTheme theme; private final SpannableTheme mTheme;
private final List<Cell> cells; private final List<Cell> mCells;
private final List<StaticLayout> layouts; private final List<StaticLayout> mLayouts;
private final TextPaint textPaint; private final TextPaint mTextPaint;
private final boolean header; private final boolean mHeader;
private final boolean odd; private final boolean mOdd;
private final Rect rect = ObjectsPool.rect(); private final Rect mRect = ObjectsPool.rect();
private final Paint paint = ObjectsPool.paint(); private final Paint mPaint = ObjectsPool.paint();
private int width; private int mWidth;
private int height; private int mHeight;
private Invalidator invalidator; private Invalidator mInvalidator;
public TableRowSpan( public TableRowSpan(
@NonNull SpannableTheme theme, @NonNull SpannableTheme theme,
@NonNull List<Cell> cells, @NonNull List<Cell> cells,
boolean header, boolean header,
boolean odd) { boolean odd) {
this.theme = theme; mTheme = theme;
this.cells = cells; mCells = cells;
this.layouts = new ArrayList<>(cells.size()); mLayouts = new ArrayList<>(cells.size());
this.textPaint = new TextPaint(); mTextPaint = new TextPaint();
this.header = header; mHeader = header;
this.odd = odd; mOdd = odd;
} }
@Override @Override
@ -97,14 +97,14 @@ public class TableRowSpan extends ReplacementSpan {
@Nullable Paint.FontMetricsInt fm) { @Nullable Paint.FontMetricsInt fm) {
// it's our absolute requirement to have width of the canvas here... because, well, it changes // 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) { if (fm != null) {
int max = 0; int max = 0;
for (StaticLayout layout : layouts) { for (StaticLayout layout : mLayouts) {
final int height = layout.getHeight(); final int height = layout.getHeight();
if (height > max) { if (height > max) {
max = height; max = height;
@ -112,10 +112,10 @@ public class TableRowSpan extends ReplacementSpan {
} }
// we store actual height // we store actual height
height = max; mHeight = max;
// but apply height with padding // but apply height with padding
final int padding = theme.tableCellPadding() * 2; final int padding = mTheme.tableCellPadding() * 2;
fm.ascent = -(max + padding); fm.ascent = -(max + padding);
fm.descent = 0; fm.descent = 0;
@ -125,7 +125,7 @@ public class TableRowSpan extends ReplacementSpan {
} }
} }
return width; return mWidth;
} }
@Override @Override
@ -141,46 +141,46 @@ public class TableRowSpan extends ReplacementSpan {
@NonNull Paint paint) { @NonNull Paint paint) {
if (recreateLayouts(canvas.getWidth())) { if (recreateLayouts(canvas.getWidth())) {
width = canvas.getWidth(); mWidth = canvas.getWidth();
textPaint.set(paint); mTextPaint.set(paint);
makeNewLayouts(); makeNewLayouts();
} }
int maxHeight = 0; 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... // 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(); final int save = canvas.save();
try { try {
rect.set(0, 0, width, bottom - top); mRect.set(0, 0, mWidth, bottom - top);
theme.applyTableOddRowStyle(this.paint); mTheme.applyTableOddRowStyle(mPaint);
canvas.translate(x, top - heightDiff); canvas.translate(x, top - heightDiff);
canvas.drawRect(rect, this.paint); canvas.drawRect(mRect, mPaint);
} finally { } finally {
canvas.restoreToCount(save); 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; StaticLayout layout;
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
layout = layouts.get(i); layout = mLayouts.get(i);
final int save = canvas.save(); final int save = canvas.save();
try { try {
canvas.translate(x + (i * w), top - heightDiff); canvas.translate(x + (i * w), top - heightDiff);
canvas.drawRect(rect, this.paint); canvas.drawRect(mRect, mPaint);
canvas.translate(padding, padding + heightDiff); canvas.translate(padding, padding + heightDiff);
layout.draw(canvas); layout.draw(canvas);
@ -194,40 +194,40 @@ public class TableRowSpan extends ReplacementSpan {
} }
} }
if (height != maxHeight) { if (mHeight != maxHeight) {
if (invalidator != null) { if (mInvalidator != null) {
invalidator.invalidate(); mInvalidator.invalidate();
} }
} }
} }
private boolean recreateLayouts(int newWidth) { private boolean recreateLayouts(int newWidth) {
return width != newWidth; return mWidth != newWidth;
} }
private void makeNewLayouts() { private void makeNewLayouts() {
textPaint.setFakeBoldText(header); mTextPaint.setFakeBoldText(mHeader);
final int columns = cells.size(); final int columns = mCells.size();
final int padding = theme.tableCellPadding() * 2; final int padding = mTheme.tableCellPadding() * 2;
final int w = (width / columns) - padding; final int w = (mWidth / columns) - padding;
this.layouts.clear(); mLayouts.clear();
Cell cell; Cell cell;
StaticLayout layout; StaticLayout layout;
for (int i = 0, size = cells.size(); i < size; i++) { for (int i = 0, size = mCells.size(); i < size; i++) {
cell = cells.get(i); cell = mCells.get(i);
layout = new StaticLayout( layout = new StaticLayout(
cell.text, cell.mText,
textPaint, mTextPaint,
w, w,
alignment(cell.alignment), alignment(cell.mAlignment),
1.F, 1.F,
.0F, .0F,
false false
); );
layouts.add(layout); mLayouts.add(layout);
} }
} }
@ -249,7 +249,7 @@ public class TableRowSpan extends ReplacementSpan {
} }
public TableRowSpan invalidator(Invalidator invalidator) { public TableRowSpan invalidator(Invalidator invalidator) {
this.invalidator = invalidator; mInvalidator = invalidator;
return this; return this;
} }
} }

View File

@ -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_1 = new Point(7.F / 18, 12.5F / 18);
private static final Point POINT_2 = new Point(15.25F / 18, 4.75F / 18); private static final Point POINT_2 = new Point(15.25F / 18, 4.75F / 18);
private final int checkedFillColor; private final int mCheckedFillColor;
private final int normalOutlineColor; private final int mNormalOutlineColor;
private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final RectF rectF = new RectF(); private final RectF mRectF = new RectF();
private final Paint checkMarkPaint = new Paint(Paint.ANTI_ALIAS_FLAG); private final Paint mCheckMarkPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final Path checkMarkPath = new Path(); private final Path mCheckMarkPath = new Path();
private boolean isChecked; private boolean mIsChecked;
// unfortunately we cannot rely on TextView to be LAYER_TYPE_SOFTWARE // 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) { public TaskListDrawable(@ColorInt int checkedFillColor, @ColorInt int normalOutlineColor, @ColorInt int checkMarkColor) {
this.checkedFillColor = checkedFillColor; mCheckedFillColor = checkedFillColor;
this.normalOutlineColor = normalOutlineColor; mNormalOutlineColor = normalOutlineColor;
checkMarkPaint.setColor(checkMarkColor); mCheckMarkPaint.setColor(checkMarkColor);
checkMarkPaint.setStyle(Paint.Style.STROKE); mCheckMarkPaint.setStyle(Paint.Style.STROKE);
} }
@Override @Override
@ -56,16 +56,16 @@ public class TaskListDrawable extends Drawable {
final float stroke = min / 8; final float stroke = min / 8;
final float side = min - stroke; final float side = min - stroke;
rectF.set(0, 0, side, side); mRectF.set(0, 0, side, side);
paint.setStrokeWidth(stroke); mPaint.setStrokeWidth(stroke);
checkMarkPaint.setStrokeWidth(stroke); mCheckMarkPaint.setStrokeWidth(stroke);
checkMarkPath.reset(); mCheckMarkPath.reset();
POINT_0.moveTo(checkMarkPath, side); POINT_0.moveTo(mCheckMarkPath, side);
POINT_1.lineTo(checkMarkPath, side); POINT_1.lineTo(mCheckMarkPath, side);
POINT_2.lineTo(checkMarkPath, side); POINT_2.lineTo(mCheckMarkPath, side);
} }
@Override @Override
@ -74,32 +74,32 @@ public class TaskListDrawable extends Drawable {
final Paint.Style style; final Paint.Style style;
final int color; final int color;
if (isChecked) { if (mIsChecked) {
style = Paint.Style.FILL_AND_STROKE; style = Paint.Style.FILL_AND_STROKE;
color = checkedFillColor; color = mCheckedFillColor;
} else { } else {
style = Paint.Style.STROKE; style = Paint.Style.STROKE;
color = normalOutlineColor; color = mNormalOutlineColor;
} }
paint.setStyle(style); mPaint.setStyle(style);
paint.setColor(color); mPaint.setColor(color);
final Rect bounds = getBounds(); final Rect bounds = getBounds();
final float left = (bounds.width() - rectF.width()) / 2; final float left = (bounds.width() - mRectF.width()) / 2;
final float top = (bounds.height() - rectF.height()) / 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(); final int save = canvas.save();
try { try {
canvas.translate(left, top); canvas.translate(left, top);
canvas.drawRoundRect(rectF, radius, radius, paint); canvas.drawRoundRect(mRectF, radius, radius, mPaint);
if (isChecked) { if (mIsChecked) {
canvas.drawPath(checkMarkPath, checkMarkPaint); canvas.drawPath(mCheckMarkPath, mCheckMarkPaint);
} }
} finally { } finally {
canvas.restoreToCount(save); canvas.restoreToCount(save);
@ -108,12 +108,12 @@ public class TaskListDrawable extends Drawable {
@Override @Override
public void setAlpha(@IntRange(from = 0, to = 255) int alpha) { public void setAlpha(@IntRange(from = 0, to = 255) int alpha) {
paint.setAlpha(alpha); mPaint.setAlpha(alpha);
} }
@Override @Override
public void setColorFilter(@Nullable ColorFilter colorFilter) { public void setColorFilter(@Nullable ColorFilter colorFilter) {
paint.setColorFilter(colorFilter); mPaint.setColorFilter(colorFilter);
} }
@Override @Override
@ -150,10 +150,10 @@ public class TaskListDrawable extends Drawable {
checked = false; checked = false;
} }
final boolean result = checked != isChecked; final boolean result = checked != mIsChecked;
if (result) { if (result) {
invalidateSelf(); invalidateSelf();
isChecked = checked; mIsChecked = checked;
} }
return result; return result;

View File

@ -12,21 +12,21 @@ import android.text.style.LeadingMarginSpan;
*/ */
public class TaskListSpan implements LeadingMarginSpan { public class TaskListSpan implements LeadingMarginSpan {
private final SpannableTheme theme; private final SpannableTheme mTheme;
private final int blockIndent; private final int mBlockIndent;
private final int start; private final int mStart;
private final boolean isDone; private final boolean mIsDone;
public TaskListSpan(@NonNull SpannableTheme theme, int blockIndent, int start, boolean isDone) { public TaskListSpan(@NonNull SpannableTheme theme, int blockIndent, int start, boolean isDone) {
this.theme = theme; mTheme = theme;
this.blockIndent = blockIndent; mBlockIndent = blockIndent;
this.start = start; mStart = start;
this.isDone = isDone; mIsDone = isDone;
} }
@Override @Override
public int getLeadingMargin(boolean first) { public int getLeadingMargin(boolean first) {
return theme.getBlockMargin() * blockIndent; return mTheme.getBlockMargin() * mBlockIndent;
} }
@Override @Override
@ -36,7 +36,7 @@ public class TaskListSpan implements LeadingMarginSpan {
return; return;
} }
final Drawable drawable = theme.getTaskListDrawable(); final Drawable drawable = mTheme.getTaskListDrawable();
if (drawable == null) { if (drawable == null) {
return; return;
} }
@ -44,7 +44,7 @@ public class TaskListSpan implements LeadingMarginSpan {
final int save = c.save(); final int save = c.save();
try { try {
final int width = theme.getBlockMargin(); final int width = mTheme.getBlockMargin();
final int height = bottom - top; final int height = bottom - top;
final int w = (int) (width * .75F + .5F); final int w = (int) (width * .75F + .5F);
@ -54,7 +54,7 @@ public class TaskListSpan implements LeadingMarginSpan {
if (drawable.isStateful()) { if (drawable.isStateful()) {
final int[] state; final int[] state;
if (isDone) { if (mIsDone) {
state = new int[]{android.R.attr.state_checked}; state = new int[]{android.R.attr.state_checked};
} else { } else {
state = new int[0]; state = new int[0];
@ -62,7 +62,7 @@ public class TaskListSpan implements LeadingMarginSpan {
drawable.setState(state); 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); final int t = top + ((height - h) / 2);
c.translate(l, t); c.translate(l, t);

View File

@ -9,12 +9,12 @@ import android.text.style.LeadingMarginSpan;
public class ThematicBreakSpan implements LeadingMarginSpan { public class ThematicBreakSpan implements LeadingMarginSpan {
private final SpannableTheme theme; private final SpannableTheme mTheme;
private final Rect rect = ObjectsPool.rect(); private final Rect mRect = ObjectsPool.rect();
private final Paint paint = ObjectsPool.paint(); private final Paint mPaint = ObjectsPool.paint();
public ThematicBreakSpan(@NonNull SpannableTheme theme) { public ThematicBreakSpan(@NonNull SpannableTheme theme) {
this.theme = theme; mTheme = theme;
} }
@Override @Override
@ -27,13 +27,13 @@ public class ThematicBreakSpan implements LeadingMarginSpan {
final int middle = top + ((bottom - top) / 2); final int middle = top + ((bottom - top) / 2);
paint.set(p); mPaint.set(p);
theme.applyThematicBreakStyle(paint); 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); final int halfHeight = (int) (height / 2.F + .5F);
rect.set(x, middle - halfHeight, c.getWidth(), middle + halfHeight); mRect.set(x, middle - halfHeight, c.getWidth(), middle + halfHeight);
c.drawRect(rect, paint); c.drawRect(mRect, mPaint);
} }
} }

View File

@ -23,22 +23,22 @@ import java.util.regex.Pattern;
@SuppressWarnings("WeakerAccess") @SuppressWarnings("WeakerAccess")
class TaskListBlockParser extends AbstractBlockParser { 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; private int indent = 0;
TaskListBlockParser(@NonNull String startLine, int startIndent) { TaskListBlockParser(@NonNull String startLine, int startIndent) {
items.add(new Item(startLine, startIndent)); mItems.add(new Item(startLine, startIndent));
indent = startIndent; indent = startIndent;
} }
@Override @Override
public Block getBlock() { public Block getBlock() {
return block; return mBlock;
} }
@Override @Override
@ -69,7 +69,7 @@ class TaskListBlockParser extends AbstractBlockParser {
@Override @Override
public void addLine(CharSequence line) { public void addLine(CharSequence line) {
if (length(line) > 0) { 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; TaskListItem listItem;
for (Item item : items) { for (Item item : mItems) {
matcher = PATTERN.matcher(item.line); matcher = PATTERN.matcher(item.line);
if (!matcher.matches()) { if (!matcher.matches()) {
continue; continue;
@ -89,7 +89,7 @@ class TaskListBlockParser extends AbstractBlockParser {
.done(isDone(matcher.group(1))) .done(isDone(matcher.group(1)))
.indent(item.indent / 2); .indent(item.indent / 2);
inlineParser.parse(matcher.group(2), listItem); inlineParser.parse(matcher.group(2), listItem);
block.appendChild(listItem); mBlock.appendChild(listItem);
} }
} }

View File

@ -8,24 +8,24 @@ import org.commonmark.node.CustomNode;
@SuppressWarnings("WeakerAccess") @SuppressWarnings("WeakerAccess")
public class TaskListItem extends CustomNode { public class TaskListItem extends CustomNode {
private boolean done; private boolean mDone;
private int indent; private int mIndent;
public boolean done() { public boolean done() {
return done; return mDone;
} }
public TaskListItem done(boolean done) { public TaskListItem done(boolean done) {
this.done = done; mDone = done;
return this; return this;
} }
public int indent() { public int indent() {
return indent; return mIndent;
} }
public TaskListItem indent(int indent) { public TaskListItem indent(int indent) {
this.indent = indent; mIndent = indent;
return this; return this;
} }
} }