Merge 35a41818a58405143633fde01de413081ebeb694 into 18938a1aa2e178185de82a4f605d03a0ccb3084d

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

View File

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

View File

@ -4,7 +4,7 @@ buildscript {
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.3'
classpath 'com.android.tools.build:gradle:3.0.0'
}
}
@ -28,12 +28,12 @@ task wrapper(type: Wrapper) {
ext {
// Config
BUILD_TOOLS = '26.0.2'
TARGET_SDK = 26
BUILD_TOOLS = '27.0.0'
TARGET_SDK = 27
MIN_SDK = 16
// Dependencies
final def supportVersion = '26.1.0'
final def supportVersion = '27.0.0'
SUPPORT_ANNOTATIONS = "com.android.support:support-annotations:$supportVersion"
SUPPORT_APP_COMPAT = "com.android.support:appcompat-v7:$supportVersion"
@ -44,5 +44,5 @@ ext {
ANDROID_SVG = 'com.caverock:androidsvg:1.2.1'
ANDROID_GIF = 'pl.droidsonroids.gif:android-gif-drawable:1.2.7'
OK_HTTP = 'com.squareup.okhttp3:okhttp:3.8.0'
OK_HTTP = 'com.squareup.okhttp3:okhttp:3.9.0'
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,13 @@
package ru.noties.markwon;
import android.support.annotation.NonNull;
import android.view.View;
/**
* @author pa.gulko zTrap (25.10.2017)
* @since 1.0.1
*/
public interface ImageClickResolver {
void resolve(View view, @NonNull String link);
}

View File

@ -0,0 +1,30 @@
package ru.noties.markwon;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.provider.Browser;
import android.support.annotation.NonNull;
import android.util.Log;
import android.view.View;
/**
* @author pa.gulko zTrap (25.10.2017)
* @since 1.0.1
*/
public class ImageClickResolverDef implements ImageClickResolver {
@Override
public void resolve(View view, @NonNull String link) {
final Uri uri = Uri.parse(LinkUtils.cropImageSizes(link));
final Context context = view.getContext();
final Intent intent = new Intent(Intent.ACTION_VIEW, uri);
intent.putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName());
try {
context.startActivity(intent);
} catch (ActivityNotFoundException e) {
Log.w("LinkResolverDef", "Actvity was not found for intent, " + intent.toString());
}
}
}

View File

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

View File

@ -0,0 +1,26 @@
package ru.noties.markwon;
import android.support.annotation.NonNull;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author pa.gulko zTrap (26.10.2017)
* @since 1.0.1
*/
public class LinkUtils {
private LinkUtils() {
//no instance
}
public static String cropImageSizes(@NonNull String link){
Matcher matcher = Pattern.compile("(.*)/(\\d+)\\$(\\d+)").matcher(link);
if (matcher.matches()){
return matcher.group(1);
} else {
return link;
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -11,15 +11,15 @@ import android.text.style.MetricAffectingSpan;
public class CodeSpan extends MetricAffectingSpan implements LeadingMarginSpan {
private final SpannableTheme theme;
private final Rect rect = ObjectsPool.rect();
private final Paint paint = ObjectsPool.paint();
private final SpannableTheme mTheme;
private final Rect mRect = ObjectsPool.rect();
private final Paint mPaint = ObjectsPool.paint();
private final boolean multiline;
private final boolean mMultiline;
public CodeSpan(@NonNull SpannableTheme theme, boolean multiline) {
this.theme = theme;
this.multiline = multiline;
mTheme = theme;
mMultiline = multiline;
}
@Override
@ -30,31 +30,31 @@ public class CodeSpan extends MetricAffectingSpan implements LeadingMarginSpan {
@Override
public void updateDrawState(TextPaint ds) {
apply(ds);
if (!multiline) {
ds.bgColor = theme.getCodeBackgroundColor(ds);
if (!mMultiline) {
ds.bgColor = mTheme.getCodeBackgroundColor(ds);
}
}
private void apply(TextPaint p) {
theme.applyCodeTextStyle(p);
mTheme.applyCodeTextStyle(p);
}
@Override
public int getLeadingMargin(boolean first) {
return multiline ? theme.getCodeMultilineMargin() : 0;
return mMultiline ? mTheme.getCodeMultilineMargin() : 0;
}
@Override
public void drawLeadingMargin(Canvas c, Paint p, int x, int dir, int top, int baseline, int bottom, CharSequence text, int start, int end, boolean first, Layout layout) {
if (multiline) {
if (mMultiline) {
paint.setStyle(Paint.Style.FILL);
paint.setColor(theme.getCodeBackgroundColor(p));
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(mTheme.getCodeBackgroundColor(p));
rect.set(x, top, c.getWidth(), bottom);
mRect.set(x, top, c.getWidth(), bottom);
c.drawRect(rect, paint);
c.drawRect(mRect, mPaint);
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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