**Markwon view
- variables names are modified to better match the [google codestyle](https://source.android.com/source/code-style#follow-field-naming-conventions)
This commit is contained in:
parent
18938a1aa2
commit
92ccf583c5
@ -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'
|
||||||
}
|
}
|
||||||
|
10
build.gradle
10
build.gradle
@ -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'
|
||||||
}
|
}
|
||||||
|
@ -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 = AsyncDrawableLoader.this.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;
|
||||||
|
@ -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) {
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -0,0 +1,13 @@
|
|||||||
|
package ru.noties.markwon;
|
||||||
|
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author pa.gulko zTrap (25.10.2017)
|
||||||
|
* @since 1.0.1
|
||||||
|
*/
|
||||||
|
public interface ImageClickResolver {
|
||||||
|
|
||||||
|
void resolve(View view, @NonNull String link);
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
package ru.noties.markwon;
|
||||||
|
|
||||||
|
import android.content.ActivityNotFoundException;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.provider.Browser;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author pa.gulko zTrap (25.10.2017)
|
||||||
|
* @since 1.0.1
|
||||||
|
*/
|
||||||
|
public class ImageClickResolverDef implements ImageClickResolver {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void resolve(View view, @NonNull String link) {
|
||||||
|
final Uri uri = Uri.parse(LinkUtils.cropImageSizes(link));
|
||||||
|
final Context context = view.getContext();
|
||||||
|
final Intent intent = new Intent(Intent.ACTION_VIEW, uri);
|
||||||
|
intent.putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName());
|
||||||
|
try {
|
||||||
|
context.startActivity(intent);
|
||||||
|
} catch (ActivityNotFoundException e) {
|
||||||
|
Log.w("LinkResolverDef", "Actvity was not found for intent, " + intent.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -12,6 +12,7 @@ import android.view.View;
|
|||||||
import ru.noties.markwon.spans.LinkSpan;
|
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);
|
||||||
|
26
library/src/main/java/ru/noties/markwon/LinkUtils.java
Normal file
26
library/src/main/java/ru/noties/markwon/LinkUtils.java
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
package ru.noties.markwon;
|
||||||
|
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author pa.gulko zTrap (26.10.2017)
|
||||||
|
* @since 1.0.1
|
||||||
|
*/
|
||||||
|
public class LinkUtils {
|
||||||
|
|
||||||
|
private LinkUtils() {
|
||||||
|
//no instance
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String cropImageSizes(@NonNull String link){
|
||||||
|
Matcher matcher = Pattern.compile("(.*)/(\\d+)\\$(\\d+)").matcher(link);
|
||||||
|
if (matcher.matches()){
|
||||||
|
return matcher.group(1);
|
||||||
|
} else {
|
||||||
|
return link;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -20,44 +20,50 @@ public class SpannableConfiguration {
|
|||||||
return new Builder(context);
|
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);
|
||||||
}
|
}
|
||||||
|
@ -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() {
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
@ -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');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user