Introduced SpannableTheme
This commit is contained in:
		
							parent
							
								
									e50789bc40
								
							
						
					
					
						commit
						e0c10c658b
					
				| @ -18,4 +18,5 @@ dependencies { | ||||
|     compile project(':library-renderer') | ||||
|     compile 'ru.noties:debug:3.0.0@jar' | ||||
|     compile 'com.squareup.picasso:picasso:2.5.2' | ||||
|     compile 'com.squareup.okhttp3:okhttp:3.8.0' | ||||
| } | ||||
|  | ||||
| @ -30,7 +30,7 @@ import ru.noties.debug.Debug; | ||||
| import ru.noties.markwon.renderer.*; | ||||
| import ru.noties.markwon.spans.AsyncDrawable; | ||||
| import ru.noties.markwon.spans.CodeSpan; | ||||
| import ru.noties.markwon.spans.DrawableSpanUtils; | ||||
| import ru.noties.markwon.spans.AsyncDrawableSpanUtils; | ||||
| 
 | ||||
| public class MainActivity extends Activity { | ||||
| 
 | ||||
| @ -101,50 +101,52 @@ public class MainActivity extends Activity { | ||||
|                             .build(); | ||||
|                     final Node node = parser.parse(md); | ||||
| 
 | ||||
|                     final SpannableConfiguration configuration = SpannableConfiguration.builder(MainActivity.this) | ||||
|                             .setAsyncDrawableLoader(new AsyncDrawable.Loader() { | ||||
|                                 @Override | ||||
|                                 public void load(@NonNull String destination, @NonNull final AsyncDrawable drawable) { | ||||
|                                     Debug.i(destination); | ||||
|                                     final Target target = new Target() { | ||||
|                                         @Override | ||||
|                                         public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) { | ||||
|                                             Debug.i(); | ||||
|                                             final Drawable d = new BitmapDrawable(getResources(), bitmap); | ||||
|                                             d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight()); | ||||
|                                             drawable.setResult(d); | ||||
| //                                            textView.setText(textView.getText()); | ||||
|                                         } | ||||
| //                    final SpannableConfiguration configuration = SpannableConfiguration.builder(MainActivity.this) | ||||
| //                            .setAsyncDrawableLoader(new AsyncDrawable.Loader() { | ||||
| //                                @Override | ||||
| //                                public void load(@NonNull String destination, @NonNull final AsyncDrawable drawable) { | ||||
| //                                    Debug.i(destination); | ||||
| //                                    final Target target = new Target() { | ||||
| //                                        @Override | ||||
| //                                        public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) { | ||||
| //                                            Debug.i(); | ||||
| //                                            final Drawable d = new BitmapDrawable(getResources(), bitmap); | ||||
| //                                            d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight()); | ||||
| //                                            drawable.setResult(d); | ||||
| ////                                            textView.setText(textView.getText()); | ||||
| //                                        } | ||||
| // | ||||
| //                                        @Override | ||||
| //                                        public void onBitmapFailed(Drawable errorDrawable) { | ||||
| //                                            Debug.i(); | ||||
| //                                        } | ||||
| // | ||||
| //                                        @Override | ||||
| //                                        public void onPrepareLoad(Drawable placeHolderDrawable) { | ||||
| //                                            Debug.i(); | ||||
| //                                        } | ||||
| //                                    }; | ||||
| //                                    targets.add(target); | ||||
| // | ||||
| //                                            picasso.load(destination) | ||||
| //                                            .tag(destination) | ||||
| //                                            .into(target); | ||||
| // | ||||
| //                                } | ||||
| // | ||||
| //                                @Override | ||||
| //                                public void cancel(@NonNull String destination) { | ||||
| //                                    Debug.i(destination); | ||||
| //                                    picasso | ||||
| //                                            .cancelTag(destination); | ||||
| //                                } | ||||
| //                            }) | ||||
| //                            .setCodeConfig(CodeSpan.Config.builder().setTextSize( | ||||
| //                                    (int) (getResources().getDisplayMetrics().density * 14 + .5F) | ||||
| //                            ).setMultilineMargin((int) (getResources().getDisplayMetrics().density * 8 + .5F)).build()) | ||||
| //                            .build(); | ||||
| 
 | ||||
|                                         @Override | ||||
|                                         public void onBitmapFailed(Drawable errorDrawable) { | ||||
|                                             Debug.i(); | ||||
|                                         } | ||||
| 
 | ||||
|                                         @Override | ||||
|                                         public void onPrepareLoad(Drawable placeHolderDrawable) { | ||||
|                                             Debug.i(); | ||||
|                                         } | ||||
|                                     }; | ||||
|                                     targets.add(target); | ||||
| 
 | ||||
|                                             picasso.load(destination) | ||||
|                                             .tag(destination) | ||||
|                                             .into(target); | ||||
| 
 | ||||
|                                 } | ||||
| 
 | ||||
|                                 @Override | ||||
|                                 public void cancel(@NonNull String destination) { | ||||
|                                     Debug.i(destination); | ||||
|                                     picasso | ||||
|                                             .cancelTag(destination); | ||||
|                                 } | ||||
|                             }) | ||||
|                             .setCodeConfig(CodeSpan.Config.builder().setTextSize( | ||||
|                                     (int) (getResources().getDisplayMetrics().density * 14 + .5F) | ||||
|                             ).setMultilineMargin((int) (getResources().getDisplayMetrics().density * 8 + .5F)).build()) | ||||
|                             .build(); | ||||
|                     final SpannableConfiguration configuration = SpannableConfiguration.create(MainActivity.this); | ||||
| 
 | ||||
|                     final CharSequence text = new ru.noties.markwon.renderer.SpannableRenderer().render( | ||||
|                             configuration, | ||||
| @ -168,7 +170,7 @@ public class MainActivity extends Activity { | ||||
|                             // NB! LinkMovementMethod forces frequent updates... | ||||
|                             textView.setMovementMethod(LinkMovementMethod.getInstance()); | ||||
|                             textView.setText(text); | ||||
|                             DrawableSpanUtils.scheduleDrawables(textView); | ||||
|                             AsyncDrawableSpanUtils.scheduleDrawables(textView); | ||||
|                         } | ||||
|                     }); | ||||
|                 } | ||||
|  | ||||
| @ -53,8 +53,8 @@ | ||||
| //import ru.noties.markwon.spans.EmphasisSpan; | ||||
| //import ru.noties.markwon.spans.BulletListItemSpan; | ||||
| //import ru.noties.markwon.spans.StrongEmphasisSpan; | ||||
| //import ru.noties.markwon.spans.SubSpan; | ||||
| //import ru.noties.markwon.spans.SupSpan; | ||||
| //import ru.noties.markwon.spans.SubScriptSpan; | ||||
| //import ru.noties.markwon.spans.SuperScriptSpan; | ||||
| //import ru.noties.markwon.spans.ThematicBreakSpan; | ||||
| // | ||||
| //public class SpannableRenderer implements Renderer { | ||||
| @ -263,9 +263,9 @@ | ||||
| //                    final int end = builder.length(); | ||||
| //                    // here, additionally, we can render some tags ourselves (sup/sub) | ||||
| //                    if ("sup".equals(item.tag)) { | ||||
| //                         builder.setSpan(new SupSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); | ||||
| //                         builder.setSpan(new SuperScriptSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); | ||||
| //                    } else if("sub".equals(item.tag)) { | ||||
| //                        builder.setSpan(new SubSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); | ||||
| //                        builder.setSpan(new SubScriptSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); | ||||
| //                    } else if("del".equals(item.tag)) { | ||||
| //                        // weird, but `Html` class does not return a spannable for `<del>o</del>` | ||||
| //                        // seems like a bug | ||||
|  | ||||
| @ -5,7 +5,7 @@ | ||||
|     android:layout_width="match_parent" | ||||
|     android:layout_height="match_parent"> | ||||
| 
 | ||||
|     <TextView xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     <TextView | ||||
|         android:id="@+id/activity_main" | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="wrap_content" | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <resources> | ||||
|     <color name="colorPrimary">#3F51B5</color> | ||||
|     <color name="colorPrimaryDark">#303F9F</color> | ||||
|     <color name="colorAccent">#FF4081</color> | ||||
|     <color name="colorPrimary">#424242</color> | ||||
|     <color name="colorPrimaryDark">#212121</color> | ||||
|     <color name="colorAccent">#4caf50</color> | ||||
| </resources> | ||||
|  | ||||
| @ -3,7 +3,7 @@ buildscript { | ||||
|         jcenter() | ||||
|     } | ||||
|     dependencies { | ||||
|         classpath 'com.android.tools.build:gradle:2.3.1' | ||||
|         classpath 'com.android.tools.build:gradle:2.3.2' | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -15,8 +15,6 @@ android { | ||||
| 
 | ||||
| dependencies { | ||||
| 
 | ||||
|     compile project(':library-spans') | ||||
| 
 | ||||
|     compile SUPPORT_ANNOTATIONS | ||||
|     compile COMMON_MARK | ||||
|     compile COMMON_MARK_STRIKETHROUGHT | ||||
|  | ||||
| @ -0,0 +1,27 @@ | ||||
| package ru.noties.markwon.renderer; | ||||
| 
 | ||||
| import android.content.ActivityNotFoundException; | ||||
| import android.content.Context; | ||||
| import android.content.Intent; | ||||
| import android.net.Uri; | ||||
| import android.provider.Browser; | ||||
| import android.support.annotation.NonNull; | ||||
| import android.util.Log; | ||||
| import android.view.View; | ||||
| 
 | ||||
| import ru.noties.markwon.spans.LinkSpan; | ||||
| 
 | ||||
| class LinkResolverDef implements LinkSpan.Resolver { | ||||
|     @Override | ||||
|     public void resolve(View view, @NonNull String link) { | ||||
|         final Uri uri = Uri.parse(link); | ||||
|         final Context context = view.getContext(); | ||||
|         final Intent intent = new Intent(Intent.ACTION_VIEW, uri); | ||||
|         intent.putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName()); | ||||
|         try { | ||||
|             context.startActivity(intent); | ||||
|         } catch (ActivityNotFoundException e) { | ||||
|             Log.w("LinkResolverDef", "Actvity was not found for intent, " + intent.toString()); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -4,12 +4,8 @@ import android.content.Context; | ||||
| import android.support.annotation.NonNull; | ||||
| 
 | ||||
| import ru.noties.markwon.spans.AsyncDrawable; | ||||
| import ru.noties.markwon.spans.BlockQuoteSpan; | ||||
| import ru.noties.markwon.spans.BulletListItemSpan; | ||||
| import ru.noties.markwon.spans.CodeSpan; | ||||
| import ru.noties.markwon.spans.HeadingSpan; | ||||
| import ru.noties.markwon.spans.OrderedListItemSpan; | ||||
| import ru.noties.markwon.spans.ThematicBreakSpan; | ||||
| import ru.noties.markwon.spans.LinkSpan; | ||||
| import ru.noties.markwon.spans.SpannableTheme; | ||||
| 
 | ||||
| public class SpannableConfiguration { | ||||
| 
 | ||||
| @ -22,140 +18,81 @@ public class SpannableConfiguration { | ||||
|         return new Builder(context); | ||||
|     } | ||||
| 
 | ||||
|     private final BlockQuoteSpan.Config blockQuoteConfig; | ||||
|     private final CodeSpan.Config codeConfig; | ||||
|     private final BulletListItemSpan.Config bulletListConfig; | ||||
|     private final HeadingSpan.Config headingConfig; | ||||
|     private final ThematicBreakSpan.Config thematicConfig; | ||||
|     private final OrderedListItemSpan.Config orderedListConfig; | ||||
|     private final SpannableTheme theme; | ||||
|     private final AsyncDrawable.Loader asyncDrawableLoader; | ||||
|     private final SyntaxHighlight syntaxHighlight; | ||||
|     private final LinkSpan.Resolver linkResolver; | ||||
| 
 | ||||
|     private SpannableConfiguration(Builder builder) { | ||||
|         this.blockQuoteConfig = builder.blockQuoteConfig; | ||||
|         this.codeConfig = builder.codeConfig; | ||||
|         this.bulletListConfig = builder.bulletListConfig; | ||||
|         this.headingConfig = builder.headingConfig; | ||||
|         this.thematicConfig = builder.thematicConfig; | ||||
|         this.orderedListConfig = builder.orderedListConfig; | ||||
|         this.theme = builder.theme; | ||||
|         this.asyncDrawableLoader = builder.asyncDrawableLoader; | ||||
|         this.syntaxHighlight = builder.syntaxHighlight; | ||||
|         this.linkResolver = builder.linkResolver; | ||||
|     } | ||||
| 
 | ||||
|     public BlockQuoteSpan.Config getBlockQuoteConfig() { | ||||
|         return blockQuoteConfig; | ||||
|     public SpannableTheme theme() { | ||||
|         return theme; | ||||
|     } | ||||
| 
 | ||||
|     public CodeSpan.Config getCodeConfig() { | ||||
|         return codeConfig; | ||||
|     } | ||||
| 
 | ||||
|     public BulletListItemSpan.Config getBulletListConfig() { | ||||
|         return bulletListConfig; | ||||
|     } | ||||
| 
 | ||||
|     public HeadingSpan.Config getHeadingConfig() { | ||||
|         return headingConfig; | ||||
|     } | ||||
| 
 | ||||
|     public ThematicBreakSpan.Config getThematicConfig() { | ||||
|         return thematicConfig; | ||||
|     } | ||||
| 
 | ||||
|     public OrderedListItemSpan.Config getOrderedListConfig() { | ||||
|         return orderedListConfig; | ||||
|     } | ||||
| 
 | ||||
|     public AsyncDrawable.Loader getAsyncDrawableLoader() { | ||||
|     public AsyncDrawable.Loader asyncDrawableLoader() { | ||||
|         return asyncDrawableLoader; | ||||
|     } | ||||
| 
 | ||||
|     public SyntaxHighlight syntaxHighlight() { | ||||
|         return syntaxHighlight; | ||||
|     } | ||||
| 
 | ||||
|     public LinkSpan.Resolver linkResolver() { | ||||
|         return linkResolver; | ||||
|     } | ||||
| 
 | ||||
|     public static class Builder { | ||||
| 
 | ||||
|         private final Context context; | ||||
|         private BlockQuoteSpan.Config blockQuoteConfig; | ||||
|         private CodeSpan.Config codeConfig; | ||||
|         private BulletListItemSpan.Config bulletListConfig; | ||||
|         private HeadingSpan.Config headingConfig; | ||||
|         private ThematicBreakSpan.Config thematicConfig; | ||||
|         private OrderedListItemSpan.Config orderedListConfig; | ||||
|         private SpannableTheme theme; | ||||
|         private AsyncDrawable.Loader asyncDrawableLoader; | ||||
|         private SyntaxHighlight syntaxHighlight; | ||||
|         private LinkSpan.Resolver linkResolver; | ||||
| 
 | ||||
|         public Builder(Context context) { | ||||
|             this.context = context; | ||||
|         } | ||||
| 
 | ||||
|         public Builder setBlockQuoteConfig(@NonNull BlockQuoteSpan.Config blockQuoteConfig) { | ||||
|             this.blockQuoteConfig = blockQuoteConfig; | ||||
|         public Builder theme(SpannableTheme theme) { | ||||
|             this.theme = theme; | ||||
|             return this; | ||||
|         } | ||||
| 
 | ||||
|         public Builder setCodeConfig(@NonNull CodeSpan.Config codeConfig) { | ||||
|             this.codeConfig = codeConfig; | ||||
|             return this; | ||||
|         } | ||||
| 
 | ||||
|         public Builder setBulletListConfig(@NonNull BulletListItemSpan.Config bulletListConfig) { | ||||
|             this.bulletListConfig = bulletListConfig; | ||||
|             return this; | ||||
|         } | ||||
| 
 | ||||
|         public Builder setHeadingConfig(@NonNull HeadingSpan.Config headingConfig) { | ||||
|             this.headingConfig = headingConfig; | ||||
|             return this; | ||||
|         } | ||||
| 
 | ||||
|         public Builder setThematicConfig(@NonNull ThematicBreakSpan.Config thematicConfig) { | ||||
|             this.thematicConfig = thematicConfig; | ||||
|             return this; | ||||
|         } | ||||
| 
 | ||||
|         public Builder setOrderedListConfig(@NonNull OrderedListItemSpan.Config orderedListConfig) { | ||||
|             this.orderedListConfig = orderedListConfig; | ||||
|             return this; | ||||
|         } | ||||
| 
 | ||||
|         public Builder setAsyncDrawableLoader(AsyncDrawable.Loader asyncDrawableLoader) { | ||||
|         public Builder asyncDrawableLoader(AsyncDrawable.Loader asyncDrawableLoader) { | ||||
|             this.asyncDrawableLoader = asyncDrawableLoader; | ||||
|             return this; | ||||
|         } | ||||
| 
 | ||||
|         // todo, change to something more reliable | ||||
|         // todo, must mention that bullet/ordered/quote must have the same margin (or maybe we can just enforce it?) | ||||
|         // actually it does make sense to have `blockQuote` & `list` margins (if they are different) | ||||
|         // todo, maybe move defaults to configuration classes? | ||||
|         public Builder syntaxHighlight(SyntaxHighlight syntaxHighlight) { | ||||
|             this.syntaxHighlight = syntaxHighlight; | ||||
|             return this; | ||||
|         } | ||||
| 
 | ||||
|         public Builder linkResolver(LinkSpan.Resolver linkResolver) { | ||||
|             this.linkResolver = linkResolver; | ||||
|             return this; | ||||
|         } | ||||
| 
 | ||||
|         public SpannableConfiguration build() { | ||||
|             if (blockQuoteConfig == null) { | ||||
|                 blockQuoteConfig = new BlockQuoteSpan.Config( | ||||
|                         px(24), | ||||
|                         0, | ||||
|                         0 | ||||
|                 ); | ||||
|             } | ||||
|             if (codeConfig == null) { | ||||
|                 codeConfig = CodeSpan.Config.builder() | ||||
|                         .setMultilineMargin(px(8)) | ||||
|                         .build(); | ||||
|             } | ||||
|             if (bulletListConfig == null) { | ||||
|                 bulletListConfig = new BulletListItemSpan.Config(px(24), 0, px(8), px(1)); | ||||
|             } | ||||
|             if (headingConfig == null) { | ||||
|                 headingConfig = new HeadingSpan.Config(px(1), 0); | ||||
|             } | ||||
|             if (thematicConfig == null) { | ||||
|                 thematicConfig = new ThematicBreakSpan.Config(0, px(2)); | ||||
|             } | ||||
|             if (orderedListConfig == null) { | ||||
|                 orderedListConfig = new OrderedListItemSpan.Config(px(24), 0); | ||||
|             if (theme == null) { | ||||
|                 theme = SpannableTheme.create(context); | ||||
|             } | ||||
|             if (asyncDrawableLoader == null) { | ||||
|                 asyncDrawableLoader = new AsyncDrawableLoaderNoOp(); | ||||
|             } | ||||
|             if (syntaxHighlight == null) { | ||||
|                 syntaxHighlight = new SyntaxHighlightNoOp(); | ||||
|             } | ||||
|             if (linkResolver == null) { | ||||
|                 linkResolver = new LinkResolverDef(); | ||||
|             } | ||||
|             return new SpannableConfiguration(this); | ||||
|         } | ||||
| 
 | ||||
|         private int px(int dp) { | ||||
|             return (int) (context.getResources().getDisplayMetrics().density * dp + .5F); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -3,7 +3,6 @@ package ru.noties.markwon.renderer; | ||||
| import android.support.annotation.NonNull; | ||||
| import android.text.SpannableStringBuilder; | ||||
| import android.text.Spanned; | ||||
| import android.text.TextUtils; | ||||
| import android.text.style.StrikethroughSpan; | ||||
| import android.text.style.URLSpan; | ||||
| 
 | ||||
| @ -38,6 +37,7 @@ import ru.noties.markwon.spans.BulletListItemSpan; | ||||
| import ru.noties.markwon.spans.CodeSpan; | ||||
| import ru.noties.markwon.spans.EmphasisSpan; | ||||
| import ru.noties.markwon.spans.HeadingSpan; | ||||
| import ru.noties.markwon.spans.LinkSpan; | ||||
| import ru.noties.markwon.spans.OrderedListItemSpan; | ||||
| import ru.noties.markwon.spans.StrongEmphasisSpan; | ||||
| import ru.noties.markwon.spans.ThematicBreakSpan; | ||||
| @ -97,7 +97,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor { | ||||
|         visitChildren(blockQuote); | ||||
| 
 | ||||
|         setSpan(length, new BlockQuoteSpan( | ||||
|                 configuration.getBlockQuoteConfig(), | ||||
|                 configuration.theme(), | ||||
|                 blockQuoteIndent | ||||
|         )); | ||||
| 
 | ||||
| @ -123,7 +123,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor { | ||||
|         builder.append('\u00a0'); | ||||
| 
 | ||||
|         setSpan(length, new CodeSpan( | ||||
|                 configuration.getCodeConfig(), | ||||
|                 configuration.theme(), | ||||
|                 false | ||||
|         )); | ||||
|     } | ||||
| @ -131,17 +131,20 @@ public class SpannableMarkdownVisitor extends AbstractVisitor { | ||||
|     @Override | ||||
|     public void visit(FencedCodeBlock fencedCodeBlock) { | ||||
| 
 | ||||
| //        Debug.i(fencedCodeBlock); | ||||
| 
 | ||||
|         newLine(); | ||||
| 
 | ||||
|         final int length = builder.length(); | ||||
| 
 | ||||
|         // empty lines on top & bottom | ||||
|         builder.append('\u00a0').append('\n'); | ||||
|         builder.append(fencedCodeBlock.getLiteral()); | ||||
|         builder.append( | ||||
|                 configuration.syntaxHighlight() | ||||
|                         .highlight(fencedCodeBlock.getInfo(), fencedCodeBlock.getLiteral()) | ||||
|         ); | ||||
|         builder.append('\u00a0').append('\n'); | ||||
| 
 | ||||
|         setSpan(length, new CodeSpan( | ||||
|                 configuration.getCodeConfig(), | ||||
|                 configuration.theme(), | ||||
|                 true | ||||
|         )); | ||||
| 
 | ||||
| @ -189,7 +192,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor { | ||||
|             visitChildren(listItem); | ||||
| 
 | ||||
|             setSpan(length, new OrderedListItemSpan( | ||||
|                     configuration.getOrderedListConfig(), | ||||
|                     configuration.theme(), | ||||
|                     String.valueOf(start) + "." + '\u00a0', | ||||
|                     blockQuoteIndent, | ||||
|                     length | ||||
| @ -204,7 +207,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor { | ||||
|             visitChildren(listItem); | ||||
| 
 | ||||
|             setSpan(length, new BulletListItemSpan( | ||||
|                     configuration.getBulletListConfig(), | ||||
|                     configuration.theme(), | ||||
|                     blockQuoteIndent, | ||||
|                     listLevel - 1, | ||||
|                     length | ||||
| @ -226,7 +229,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor { | ||||
| 
 | ||||
|         final int length = builder.length(); | ||||
|         builder.append(' '); // without space it won't render | ||||
|         setSpan(length, new ThematicBreakSpan(configuration.getThematicConfig())); | ||||
|         setSpan(length, new ThematicBreakSpan(configuration.theme())); | ||||
| 
 | ||||
|         newLine(); | ||||
|         builder.append('\n'); | ||||
| @ -242,7 +245,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor { | ||||
|         final int length = builder.length(); | ||||
|         visitChildren(heading); | ||||
|         setSpan(length, new HeadingSpan( | ||||
|                 configuration.getHeadingConfig(), | ||||
|                 configuration.theme(), | ||||
|                 heading.getLevel(), | ||||
|                 builder.length()) | ||||
|         ); | ||||
| @ -315,7 +318,18 @@ public class SpannableMarkdownVisitor extends AbstractVisitor { | ||||
|             builder.append(' '); // breakable space | ||||
|         } | ||||
| 
 | ||||
|         setSpan(length, new AsyncDrawableSpan(new AsyncDrawable(image.getDestination(), configuration.getAsyncDrawableLoader()))); | ||||
|         final Node parent = image.getParent(); | ||||
|         final boolean link = parent != null && parent instanceof Link; | ||||
| 
 | ||||
|         setSpan(length, new AsyncDrawableSpan( | ||||
|                 configuration.theme(), | ||||
|                 new AsyncDrawable( | ||||
|                         image.getDestination(), | ||||
|                         configuration.asyncDrawableLoader() | ||||
|                 ), | ||||
|                 AsyncDrawableSpan.ALIGN_BOTTOM, | ||||
|                 link) | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
| @ -329,7 +343,7 @@ public class SpannableMarkdownVisitor extends AbstractVisitor { | ||||
|     public void visit(Link link) { | ||||
|         final int length = builder.length(); | ||||
|         visitChildren(link); | ||||
|         setSpan(length, new URLSpan(link.getDestination())); | ||||
|         setSpan(length, new LinkSpan(configuration.theme(), link.getDestination(), configuration.linkResolver())); | ||||
|     } | ||||
| 
 | ||||
|     private void setSpan(int start, @NonNull Object span) { | ||||
|  | ||||
| @ -14,6 +14,7 @@ public class SpannableRenderer { | ||||
|     // * Common interface for images (in markdown & inline-html) | ||||
|     // * util method to properly copy markdown (with lists/links, etc) | ||||
|     // * util to apply empty line height | ||||
|     // * transform relative urls to absolute ones... | ||||
| 
 | ||||
|     public CharSequence render(@NonNull SpannableConfiguration configuration, @Nullable Node node) { | ||||
|         final CharSequence out; | ||||
|  | ||||
| @ -0,0 +1,10 @@ | ||||
| package ru.noties.markwon.renderer; | ||||
| 
 | ||||
| import android.support.annotation.NonNull; | ||||
| import android.support.annotation.Nullable; | ||||
| 
 | ||||
| public interface SyntaxHighlight { | ||||
| 
 | ||||
|     @NonNull | ||||
|     CharSequence highlight(@Nullable String info, @NonNull String code); | ||||
| } | ||||
| @ -0,0 +1,12 @@ | ||||
| package ru.noties.markwon.renderer; | ||||
| 
 | ||||
| import android.support.annotation.NonNull; | ||||
| import android.support.annotation.Nullable; | ||||
| 
 | ||||
| class SyntaxHighlightNoOp implements SyntaxHighlight { | ||||
|     @NonNull | ||||
|     @Override | ||||
|     public CharSequence highlight(@Nullable String info, @NonNull String code) { | ||||
|         return code; | ||||
|     } | ||||
| } | ||||
| @ -3,7 +3,6 @@ package ru.noties.markwon.spans; | ||||
| import android.graphics.Canvas; | ||||
| import android.graphics.Paint; | ||||
| import android.graphics.Rect; | ||||
| import android.graphics.drawable.Drawable; | ||||
| import android.support.annotation.IntDef; | ||||
| import android.support.annotation.IntRange; | ||||
| import android.support.annotation.NonNull; | ||||
| @ -13,23 +12,39 @@ import android.text.style.ReplacementSpan; | ||||
| @SuppressWarnings("WeakerAccess") | ||||
| public class AsyncDrawableSpan extends ReplacementSpan { | ||||
| 
 | ||||
|     @IntDef({ ALIGN_BOTTOM, ALIGN_BASELINE, ALIGN_CENTER }) | ||||
|     @interface Alignment {} | ||||
|     @IntDef({ALIGN_BOTTOM, ALIGN_BASELINE, ALIGN_CENTER}) | ||||
|     @interface Alignment { | ||||
|     } | ||||
| 
 | ||||
|     public static final int ALIGN_BOTTOM = 0; | ||||
|     public static final int ALIGN_BASELINE = 1; | ||||
|     public static final int ALIGN_CENTER = 2; // will only center if drawable height is less than text line height | ||||
| 
 | ||||
|     private final SpannableTheme theme; | ||||
|     private final AsyncDrawable drawable; | ||||
|     private final int alignment; | ||||
|     private final boolean replacementTextIsLink; | ||||
| 
 | ||||
|     public AsyncDrawableSpan(@NonNull AsyncDrawable drawable) { | ||||
|         this(drawable, ALIGN_BOTTOM); | ||||
|     public AsyncDrawableSpan(@NonNull SpannableTheme theme, @NonNull AsyncDrawable drawable) { | ||||
|         this(theme, drawable, ALIGN_BOTTOM); | ||||
|     } | ||||
| 
 | ||||
|     public AsyncDrawableSpan(@NonNull AsyncDrawable drawable, @Alignment int alignment) { | ||||
|     public AsyncDrawableSpan( | ||||
|             @NonNull SpannableTheme theme, | ||||
|             @NonNull AsyncDrawable drawable, | ||||
|             @Alignment int alignment) { | ||||
|         this(theme, drawable, alignment, false); | ||||
|     } | ||||
| 
 | ||||
|     public AsyncDrawableSpan( | ||||
|             @NonNull SpannableTheme theme, | ||||
|             @NonNull AsyncDrawable drawable, | ||||
|             @Alignment int alignment, | ||||
|             boolean replacementTextIsLink) { | ||||
|         this.theme = theme; | ||||
|         this.drawable = drawable; | ||||
|         this.alignment = alignment; | ||||
|         this.replacementTextIsLink = replacementTextIsLink; | ||||
| 
 | ||||
|         // additionally set intrinsic bounds if empty | ||||
|         final Rect rect = drawable.getBounds(); | ||||
| @ -66,8 +81,13 @@ public class AsyncDrawableSpan extends ReplacementSpan { | ||||
| 
 | ||||
|         } else { | ||||
| 
 | ||||
|             size = (int) (paint.measureText(text, start, end) + .5F); | ||||
|             // we will apply style here in case if theme modifies textSize or style (affects metrics) | ||||
|             if (replacementTextIsLink) { | ||||
|                 theme.applyLinkStyle(paint); | ||||
|             } | ||||
| 
 | ||||
|             // NB, no specific text handling (no new lines, etc) | ||||
|             size = (int) (paint.measureText(text, start, end) + .5F); | ||||
|         } | ||||
| 
 | ||||
|         return size; | ||||
| @ -108,7 +128,15 @@ public class AsyncDrawableSpan extends ReplacementSpan { | ||||
|             } | ||||
|         } else { | ||||
| 
 | ||||
|             final int textY = (int) (bottom - ((bottom - top) / 2) - ((paint.descent() + paint.ascent()) / 2.F + .5F)); | ||||
|             // will it make sense to have additional background/borders for an image replacement? | ||||
|             // let's focus on main functionality and then think of it | ||||
| 
 | ||||
|             final float textY = CanvasUtils.textCenterY(top, bottom, paint); | ||||
|             if (replacementTextIsLink) { | ||||
|                 theme.applyLinkStyle(paint); | ||||
|             } | ||||
| 
 | ||||
|             // NB, no specific text handling (no new lines, etc) | ||||
|             canvas.drawText(text, start, end, x, textY, paint); | ||||
|         } | ||||
|     } | ||||
| @ -11,7 +11,7 @@ import android.widget.TextView; | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
| 
 | ||||
| public class DrawableSpanUtils { | ||||
| public class AsyncDrawableSpanUtils { | ||||
| 
 | ||||
|     // todo, add `unschedule` method (to be used when new text is set, so | ||||
|     // drawables are removed from callbacks) | ||||
| @ -66,7 +66,7 @@ public class DrawableSpanUtils { | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private DrawableSpanUtils() {} | ||||
|     private AsyncDrawableSpanUtils() {} | ||||
| 
 | ||||
|     private static class DrawableCallbackImpl implements Drawable.Callback { | ||||
| 
 | ||||
| @ -0,0 +1,51 @@ | ||||
| package ru.noties.markwon.spans; | ||||
| 
 | ||||
| import android.graphics.Canvas; | ||||
| import android.graphics.Paint; | ||||
| import android.graphics.Rect; | ||||
| import android.support.annotation.NonNull; | ||||
| import android.text.Layout; | ||||
| import android.text.style.LeadingMarginSpan; | ||||
| 
 | ||||
| public class BlockQuoteSpan implements LeadingMarginSpan { | ||||
| 
 | ||||
|     private final SpannableTheme theme; | ||||
|     private final Rect rect = ObjectsPool.rect(); | ||||
|     private final Paint paint = ObjectsPool.paint(); | ||||
|     private final int indent; | ||||
| 
 | ||||
|     public BlockQuoteSpan(@NonNull SpannableTheme theme, int indent) { | ||||
|         this.theme = theme; | ||||
|         this.indent = indent; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public int getLeadingMargin(boolean first) { | ||||
|         return theme.getBlockMargin(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void drawLeadingMargin( | ||||
|             Canvas c, | ||||
|             Paint p, | ||||
|             int x, | ||||
|             int dir, | ||||
|             int top, | ||||
|             int baseline, | ||||
|             int bottom, | ||||
|             CharSequence text, | ||||
|             int start, | ||||
|             int end, | ||||
|             boolean first, | ||||
|             Layout layout) { | ||||
| 
 | ||||
|         final int width = theme.getBlockQuoteWidth(); | ||||
| 
 | ||||
|         theme.applyBlockQuoteStyle(paint); | ||||
| 
 | ||||
|         final int left = theme.getBlockMargin() * (indent - 1); | ||||
|         rect.set(left, top, left + width, bottom); | ||||
| 
 | ||||
|         c.drawRect(rect, paint); | ||||
|     } | ||||
| } | ||||
| @ -4,7 +4,6 @@ import android.graphics.Canvas; | ||||
| import android.graphics.Paint; | ||||
| import android.graphics.Rect; | ||||
| import android.graphics.RectF; | ||||
| import android.support.annotation.ColorInt; | ||||
| import android.support.annotation.IntRange; | ||||
| import android.support.annotation.NonNull; | ||||
| import android.text.Layout; | ||||
| @ -12,30 +11,7 @@ import android.text.style.LeadingMarginSpan; | ||||
| 
 | ||||
| public class BulletListItemSpan implements LeadingMarginSpan { | ||||
| 
 | ||||
|     // todo, there are 3 types of bullets: filled circle, stroke circle & filled rectangle | ||||
|     // also, there are ordered lists | ||||
| 
 | ||||
|     public static class Config { | ||||
| 
 | ||||
|         final int marginWidth; | ||||
|         final int bulletColor; // by default uses text color | ||||
|         final int bulletSide; | ||||
|         final int bulletStrokeWidth; | ||||
| 
 | ||||
|         // from 0 but it makes sense to provide something wider | ||||
|         public Config( | ||||
|                 @IntRange(from = 0) int marginWidth, | ||||
|                 @ColorInt int bulletColor, | ||||
|                 @IntRange(from = 0) int bulletSide, | ||||
|                 @IntRange(from = 0) int bulletStrokeWidth) { | ||||
|             this.marginWidth = marginWidth; | ||||
|             this.bulletColor = bulletColor; | ||||
|             this.bulletSide = bulletSide; | ||||
|             this.bulletStrokeWidth = bulletStrokeWidth; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private final Config config; | ||||
|     private SpannableTheme theme; | ||||
| 
 | ||||
|     private final Paint paint = ObjectsPool.paint(); | ||||
|     private final RectF circle = ObjectsPool.rectF(); | ||||
| @ -46,11 +22,11 @@ public class BulletListItemSpan implements LeadingMarginSpan { | ||||
|     private final int start; | ||||
| 
 | ||||
|     public BulletListItemSpan( | ||||
|             @NonNull Config config, | ||||
|             @NonNull SpannableTheme theme, | ||||
|             @IntRange(from = 0) int blockIndent, | ||||
|             @IntRange(from = 0) int level, | ||||
|             @IntRange(from = 0) int start) { | ||||
|         this.config = config; | ||||
|         this.theme = theme; | ||||
|         this.blockIndent = blockIndent; | ||||
|         this.level = level; | ||||
|         this.start = start; | ||||
| @ -58,7 +34,7 @@ public class BulletListItemSpan implements LeadingMarginSpan { | ||||
| 
 | ||||
|     @Override | ||||
|     public int getLeadingMargin(boolean first) { | ||||
|         return config.marginWidth; | ||||
|         return theme.getBlockMargin(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
| @ -69,39 +45,17 @@ public class BulletListItemSpan implements LeadingMarginSpan { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         final int color; | ||||
|         final float stroke; | ||||
|         paint.set(p); | ||||
| 
 | ||||
|         if (config.bulletColor == 0) { | ||||
|             color = p.getColor(); | ||||
|         } else { | ||||
|             color = config.bulletColor; | ||||
|         } | ||||
| 
 | ||||
|         if (config.bulletStrokeWidth == 0) { | ||||
|             stroke = p.getStrokeWidth(); | ||||
|         } else { | ||||
|             stroke = config.bulletStrokeWidth; | ||||
|         } | ||||
| 
 | ||||
|         paint.setColor(color); | ||||
|         paint.setStrokeWidth(stroke); | ||||
|         theme.applyListItemStyle(paint); | ||||
| 
 | ||||
|         final int save = c.save(); | ||||
|         try { | ||||
| 
 | ||||
|             // by default we use half of margin width, but if height is less than width, we calculate from it | ||||
|             final int width = config.marginWidth; | ||||
|             final int width = theme.getBlockMargin(); | ||||
|             final int height = bottom - top; | ||||
| 
 | ||||
|             final int min = Math.min(config.marginWidth, height) / 2; | ||||
|             final int side; | ||||
|             if (config.bulletSide == 0 | ||||
|                     || config.bulletSide > min) { | ||||
|                 side = min; | ||||
|             } else { | ||||
|                 side = config.bulletSide; | ||||
|             } | ||||
|             final int side = theme.getBulletWidth(bottom - top); | ||||
| 
 | ||||
|             final int marginLeft = (width - side) / 2; | ||||
|             final int marginTop = (height - side) / 2; | ||||
| @ -0,0 +1,14 @@ | ||||
| package ru.noties.markwon.spans; | ||||
| 
 | ||||
| import android.graphics.Paint; | ||||
| import android.support.annotation.NonNull; | ||||
| 
 | ||||
| abstract class CanvasUtils { | ||||
| 
 | ||||
|     static float textCenterY(int top, int bottom, @NonNull Paint paint) { | ||||
|         return (int) (bottom - ((bottom - top) / 2) - ((paint.descent() + paint.ascent()) / 2.F + .5F)); | ||||
|     } | ||||
| 
 | ||||
|     private CanvasUtils() { | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,60 @@ | ||||
| package ru.noties.markwon.spans; | ||||
| 
 | ||||
| import android.graphics.Canvas; | ||||
| import android.graphics.Paint; | ||||
| import android.graphics.Rect; | ||||
| import android.support.annotation.NonNull; | ||||
| import android.text.Layout; | ||||
| import android.text.TextPaint; | ||||
| import android.text.style.LeadingMarginSpan; | ||||
| import android.text.style.MetricAffectingSpan; | ||||
| 
 | ||||
| public class CodeSpan extends MetricAffectingSpan implements LeadingMarginSpan { | ||||
| 
 | ||||
|     private final SpannableTheme theme; | ||||
|     private final Rect rect = ObjectsPool.rect(); | ||||
|     private final Paint paint = ObjectsPool.paint(); | ||||
| 
 | ||||
|     private final boolean multiline; | ||||
| 
 | ||||
|     public CodeSpan(@NonNull SpannableTheme theme, boolean multiline) { | ||||
|         this.theme = theme; | ||||
|         this.multiline = multiline; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void updateMeasureState(TextPaint p) { | ||||
|         apply(p); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void updateDrawState(TextPaint ds) { | ||||
|         apply(ds); | ||||
|         if (!multiline) { | ||||
|             ds.bgColor = theme.getCodeBackgroundColor(ds); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private void apply(TextPaint p) { | ||||
|         theme.applyCodeTextStyle(p); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public int getLeadingMargin(boolean first) { | ||||
|         return multiline ? theme.getCodeMultilineMargin() : 0; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void drawLeadingMargin(Canvas c, Paint p, int x, int dir, int top, int baseline, int bottom, CharSequence text, int start, int end, boolean first, Layout layout) { | ||||
| 
 | ||||
|         if (multiline) { | ||||
| 
 | ||||
|             paint.setStyle(Paint.Style.FILL); | ||||
|             paint.setColor(theme.getCodeBackgroundColor(p)); | ||||
| 
 | ||||
|             rect.set(x, top, c.getWidth(), bottom); | ||||
| 
 | ||||
|             c.drawRect(rect, paint); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -1,6 +1,6 @@ | ||||
| package ru.noties.markwon.spans; | ||||
| 
 | ||||
| class ColorUtils { | ||||
| abstract class ColorUtils { | ||||
| 
 | ||||
|     static int applyAlpha(int color, int alpha) { | ||||
|         return (color & 0x00FFFFFF) | (alpha << 24); | ||||
| @ -0,0 +1,67 @@ | ||||
| package ru.noties.markwon.spans; | ||||
| 
 | ||||
| import android.graphics.Canvas; | ||||
| import android.graphics.Paint; | ||||
| import android.graphics.Rect; | ||||
| import android.support.annotation.IntRange; | ||||
| import android.support.annotation.NonNull; | ||||
| import android.text.Layout; | ||||
| import android.text.TextPaint; | ||||
| import android.text.style.LeadingMarginSpan; | ||||
| import android.text.style.MetricAffectingSpan; | ||||
| 
 | ||||
| public class HeadingSpan extends MetricAffectingSpan implements LeadingMarginSpan { | ||||
| 
 | ||||
|     private final SpannableTheme theme; | ||||
|     private final Rect rect = ObjectsPool.rect(); | ||||
|     private final Paint paint = ObjectsPool.paint(); | ||||
|     private final int level; | ||||
|     private final int end; | ||||
| 
 | ||||
|     public HeadingSpan(@NonNull SpannableTheme theme, @IntRange(from = 1, to = 6) int level, @IntRange(from = 0) int end) { | ||||
|         this.theme = theme; | ||||
|         this.level = level; | ||||
|         this.end = end; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void updateMeasureState(TextPaint p) { | ||||
|         apply(p); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void updateDrawState(TextPaint tp) { | ||||
|         apply(tp); | ||||
|     } | ||||
| 
 | ||||
|     private void apply(TextPaint paint) { | ||||
|         theme.applyHeadingTextStyle(paint, level); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public int getLeadingMargin(boolean first) { | ||||
|         // no margin actually, but we need to access Canvas to draw break | ||||
|         return 0; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void drawLeadingMargin(Canvas c, Paint p, int x, int dir, int top, int baseline, int bottom, CharSequence text, int start, int end, boolean first, Layout layout) { | ||||
| 
 | ||||
|         if (level == 1 | ||||
|                 || level == 2) { | ||||
| 
 | ||||
|             if (this.end == end) { | ||||
| 
 | ||||
|                 paint.set(p); | ||||
| 
 | ||||
|                 theme.applyHeadingBreakStyle(paint); | ||||
| 
 | ||||
|                 final float height = paint.getStrokeWidth(); | ||||
|                 final int b = (int) (bottom - height + .5F); | ||||
| 
 | ||||
|                 rect.set(x, b, c.getWidth(), bottom); | ||||
|                 c.drawRect(rect, paint); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,33 @@ | ||||
| package ru.noties.markwon.spans; | ||||
| 
 | ||||
| import android.support.annotation.NonNull; | ||||
| import android.text.TextPaint; | ||||
| import android.text.style.ClickableSpan; | ||||
| import android.view.View; | ||||
| 
 | ||||
| public class LinkSpan extends ClickableSpan { | ||||
| 
 | ||||
|     public interface Resolver { | ||||
|         void resolve(View view, @NonNull String link); | ||||
|     } | ||||
| 
 | ||||
|     private final SpannableTheme theme; | ||||
|     private final String link; | ||||
|     private final Resolver resolver; | ||||
| 
 | ||||
|     public LinkSpan(@NonNull SpannableTheme theme, @NonNull String link, @NonNull Resolver resolver) { | ||||
|         this.theme = theme; | ||||
|         this.link = link; | ||||
|         this.resolver = resolver; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onClick(View widget) { | ||||
|         resolver.resolve(widget, link); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void updateDrawState(TextPaint ds) { | ||||
|         theme.applyLinkStyle(ds); | ||||
|     } | ||||
| } | ||||
| @ -2,7 +2,6 @@ package ru.noties.markwon.spans; | ||||
| 
 | ||||
| import android.graphics.Canvas; | ||||
| import android.graphics.Paint; | ||||
| import android.support.annotation.ColorInt; | ||||
| import android.support.annotation.IntRange; | ||||
| import android.support.annotation.NonNull; | ||||
| import android.text.Layout; | ||||
| @ -10,29 +9,18 @@ import android.text.style.LeadingMarginSpan; | ||||
| 
 | ||||
| public class OrderedListItemSpan implements LeadingMarginSpan { | ||||
| 
 | ||||
|     public static class Config { | ||||
| 
 | ||||
|         final int marginWidth; // by default 0 | ||||
|         final int numberColor; // by default color of the main text | ||||
| 
 | ||||
|         public Config(@IntRange(from = 0) int marginWidth, @ColorInt int numberColor) { | ||||
|             this.marginWidth = marginWidth; | ||||
|             this.numberColor = numberColor; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private final Config config; | ||||
|     private final SpannableTheme theme; | ||||
|     private final String number; | ||||
|     private final int blockIndent; | ||||
|     private final int start; | ||||
| 
 | ||||
|     public OrderedListItemSpan( | ||||
|             @NonNull Config config, | ||||
|             @NonNull SpannableTheme theme, | ||||
|             @NonNull String number, | ||||
|             @IntRange(from = 0) int blockIndent, | ||||
|             @IntRange(from = 0) int start | ||||
|     ) { | ||||
|         this.config = config; | ||||
|         this.theme = theme; | ||||
|         this.number = number; | ||||
|         this.blockIndent = blockIndent; | ||||
|         this.start = start; | ||||
| @ -40,7 +28,7 @@ public class OrderedListItemSpan implements LeadingMarginSpan { | ||||
| 
 | ||||
|     @Override | ||||
|     public int getLeadingMargin(boolean first) { | ||||
|         return config.marginWidth; | ||||
|         return theme.getBlockMargin(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
| @ -51,15 +39,13 @@ public class OrderedListItemSpan implements LeadingMarginSpan { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         if (config.numberColor != 0) { | ||||
|             p.setColor(config.numberColor); | ||||
|         } | ||||
|         theme.applyListItemStyle(p); | ||||
| 
 | ||||
|         final int width = config.marginWidth; | ||||
|         final int width = theme.getBlockMargin(); | ||||
|         final int numberWidth = (int) (p.measureText(number) + .5F); | ||||
|         final int numberX = (width * blockIndent) - numberWidth; | ||||
| 
 | ||||
|         final int numberY = bottom - ((bottom - top) / 2) - (int) ((p.descent() + p.ascent()) / 2); | ||||
|         final float numberY = CanvasUtils.textCenterY(top, bottom, p); | ||||
| 
 | ||||
|         c.drawText(number, numberX, numberY, p); | ||||
|     } | ||||
| @ -0,0 +1,456 @@ | ||||
| package ru.noties.markwon.spans; | ||||
| 
 | ||||
| import android.content.Context; | ||||
| import android.content.res.TypedArray; | ||||
| import android.graphics.Paint; | ||||
| import android.graphics.Typeface; | ||||
| import android.support.annotation.AttrRes; | ||||
| import android.support.annotation.IntRange; | ||||
| import android.support.annotation.NonNull; | ||||
| import android.text.TextPaint; | ||||
| import android.util.TypedValue; | ||||
| import android.widget.TextView; | ||||
| 
 | ||||
| @SuppressWarnings("WeakerAccess") | ||||
| public class SpannableTheme { | ||||
| 
 | ||||
|     // this method should be used if TextView is known beforehand | ||||
|     // it will correctly measure the `space` char and set it as `codeMultilineMargin` | ||||
|     // otherwise this value must be set explicitly ( | ||||
|     public static SpannableTheme create(@NonNull TextView textView) { | ||||
|         return builderWithDefaults(textView.getContext()) | ||||
|                 .codeMultilineMargin((int) (textView.getPaint().measureText("\u00a0") + .5F)) | ||||
|                 .build(); | ||||
|     } | ||||
| 
 | ||||
|     // this create default theme (except for `codeMultilineMargin` property) | ||||
|     public static SpannableTheme create(@NonNull Context context) { | ||||
|         return builderWithDefaults(context).build(); | ||||
|     } | ||||
| 
 | ||||
|     public static Builder builder() { | ||||
|         return new Builder(); | ||||
|     } | ||||
| 
 | ||||
|     public static Builder builder(@NonNull SpannableTheme copyFrom) { | ||||
|         return new Builder(copyFrom); | ||||
|     } | ||||
| 
 | ||||
|     public static Builder builderWithDefaults(@NonNull Context context) { | ||||
|         final Px px = new Px(context); | ||||
|         return new Builder() | ||||
|                 .linkColor(resolve(context, android.R.attr.textColorLink)) | ||||
|                 .codeMultilineMargin(px.px(8)) | ||||
|                 .blockMargin(px.px(24)) | ||||
|                 .bulletListItemStrokeWidth(px.px(1)) | ||||
|                 .headingBreakHeight(px.px(1)) | ||||
|                 .thematicBreakHeight(px.px(2)); | ||||
|     } | ||||
| 
 | ||||
|     private static int resolve(Context context, @AttrRes int attr) { | ||||
|         final TypedValue typedValue = new TypedValue(); | ||||
|         final int attrs[] = new int[]{attr}; | ||||
|         final TypedArray typedArray = context.obtainStyledAttributes(typedValue.data, attrs); | ||||
|         try { | ||||
|             return typedArray.getColor(0, 0); | ||||
|         } finally { | ||||
|             typedArray.recycle(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     protected static final int BLOCK_QUOTE_DEF_COLOR_ALPHA = 50; | ||||
| 
 | ||||
|     protected static final int CODE_DEF_BACKGROUND_COLOR_ALPHA = 25; | ||||
|     protected static final float CODE_DEF_TEXT_SIZE_RATIO = .87F; | ||||
| 
 | ||||
|     protected static final int HEADING_DEF_BREAK_COLOR_ALPHA = 75; | ||||
| 
 | ||||
|     // taken from html spec (most browsers render headings like that) | ||||
|     // is not exposed via protected modifier in order to disallow modification | ||||
|     private static final float[] HEADING_SIZES = { | ||||
|             2.F, 1.5F, 1.17F, 1.F, .83F, .67F, | ||||
|     }; | ||||
| 
 | ||||
|     protected static final float SCRIPT_DEF_TEXT_SIZE_RATIO = .75F; | ||||
| 
 | ||||
|     protected static final int THEMATIC_BREAK_DEF_ALPHA = 75; | ||||
| 
 | ||||
|     protected final int linkColor; | ||||
| 
 | ||||
|     // used in quote, lists | ||||
|     protected final int blockMargin; | ||||
| 
 | ||||
|     // by default it's 1/4th of `blockMargin` | ||||
|     protected final int blockQuoteWidth; | ||||
| 
 | ||||
|     // by default it's text color with `BLOCK_QUOTE_DEF_COLOR_ALPHA` applied alpha | ||||
|     protected final int blockQuoteColor; | ||||
| 
 | ||||
|     // by default uses text color (applied for un-ordered lists & ordered (bullets & numbers) | ||||
|     protected final int listItemColor; | ||||
| 
 | ||||
|     // by default the stroke color of a paint object | ||||
|     protected final int bulletListItemStrokeWidth; | ||||
| 
 | ||||
|     // width of bullet, by default min(blockMargin, height) / 2 | ||||
|     protected final int bulletWidth; | ||||
| 
 | ||||
|     // by default - main text color | ||||
|     protected final int codeTextColor; | ||||
| 
 | ||||
|     // by default 0.1 alpha of textColor/codeTextColor | ||||
|     protected final int codeBackgroundColor; | ||||
| 
 | ||||
|     // by default `width` of a space char... it's fun and games, but span doesn't have access to paint in `getLeadingMargin` | ||||
|     // so, we need to set this value explicitly (think of an utility method, that takes TextView/TextPaint and measures space char) | ||||
|     protected final int codeMultilineMargin; | ||||
| 
 | ||||
|     // by default Typeface.MONOSPACE | ||||
|     protected final Typeface codeTypeface; | ||||
| 
 | ||||
|     // by default a bit (how much?!) smaller than normal text | ||||
|     // applied ONLY if default typeface was used, otherwise, not applied | ||||
|     protected final int codeTextSize; | ||||
| 
 | ||||
|     // by default paint.getStrokeWidth | ||||
|     protected final int headingBreakHeight; | ||||
| 
 | ||||
|     // by default, text color with `HEADING_DEF_BREAK_COLOR_ALPHA` applied alpha | ||||
|     protected final int headingBreakColor; | ||||
| 
 | ||||
|     // by default `SCRIPT_DEF_TEXT_SIZE_RATIO` | ||||
|     protected final float scriptTextSizeRatio; | ||||
| 
 | ||||
|     // by default textColor with `THEMATIC_BREAK_DEF_ALPHA` applied alpha | ||||
|     protected final int thematicBreakColor; | ||||
| 
 | ||||
|     // by default paint.strokeWidth | ||||
|     protected final int thematicBreakHeight; | ||||
| 
 | ||||
|     protected SpannableTheme(@NonNull Builder builder) { | ||||
|         this.linkColor = builder.linkColor; | ||||
|         this.blockMargin = builder.blockMargin; | ||||
|         this.blockQuoteWidth = builder.blockQuoteWidth; | ||||
|         this.blockQuoteColor = builder.blockQuoteColor; | ||||
|         this.listItemColor = builder.listItemColor; | ||||
|         this.bulletListItemStrokeWidth = builder.bulletListItemStrokeWidth; | ||||
|         this.bulletWidth = builder.bulletWidth; | ||||
|         this.codeTextColor = builder.codeTextColor; | ||||
|         this.codeBackgroundColor = builder.codeBackgroundColor; | ||||
|         this.codeMultilineMargin = builder.codeMultilineMargin; | ||||
|         this.codeTypeface = builder.codeTypeface; | ||||
|         this.codeTextSize = builder.codeTextSize; | ||||
|         this.headingBreakHeight = builder.headingBreakHeight; | ||||
|         this.headingBreakColor = builder.headingBreakColor; | ||||
|         this.scriptTextSizeRatio = builder.scriptTextSizeRatio; | ||||
|         this.thematicBreakColor = builder.thematicBreakColor; | ||||
|         this.thematicBreakHeight = builder.thematicBreakHeight; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     public void applyLinkStyle(@NonNull Paint paint) { | ||||
|         paint.setUnderlineText(true); | ||||
|         if (linkColor != 0) { | ||||
|             // by default we will be using text color | ||||
|             paint.setColor(linkColor); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public void applyBlockQuoteStyle(@NonNull Paint paint) { | ||||
|         final int color; | ||||
|         if (blockQuoteColor == 0) { | ||||
|             color = ColorUtils.applyAlpha(paint.getColor(), BLOCK_QUOTE_DEF_COLOR_ALPHA); | ||||
|         } else { | ||||
|             color = blockQuoteColor; | ||||
|         } | ||||
|         paint.setStyle(Paint.Style.FILL); | ||||
|         paint.setColor(color); | ||||
|     } | ||||
| 
 | ||||
|     public int getBlockMargin() { | ||||
|         return blockMargin; | ||||
|     } | ||||
| 
 | ||||
|     public int getBlockQuoteWidth() { | ||||
|         final int out; | ||||
|         if (blockQuoteWidth == 0) { | ||||
|             out = (int) (blockMargin * .25F + .5F); | ||||
|         } else { | ||||
|             out = blockQuoteWidth; | ||||
|         } | ||||
|         return out; | ||||
|     } | ||||
| 
 | ||||
|     public void applyListItemStyle(@NonNull Paint paint) { | ||||
| 
 | ||||
|         final int color; | ||||
|         if (listItemColor != 0) { | ||||
|             color = listItemColor; | ||||
|         } else { | ||||
|             color = paint.getColor(); | ||||
|         } | ||||
|         paint.setColor(color); | ||||
| 
 | ||||
|         if (bulletListItemStrokeWidth != 0) { | ||||
|             paint.setStrokeWidth(bulletListItemStrokeWidth); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public int getBulletWidth(int height) { | ||||
| 
 | ||||
|         final int min = Math.min(blockMargin, height) / 2; | ||||
| 
 | ||||
|         final int width; | ||||
|         if (bulletWidth == 0 | ||||
|                 || bulletWidth > min) { | ||||
|             width = min; | ||||
|         } else { | ||||
|             width = bulletWidth; | ||||
|         } | ||||
| 
 | ||||
|         return width; | ||||
|     } | ||||
| 
 | ||||
|     public void applyCodeTextStyle(@NonNull Paint paint) { | ||||
| 
 | ||||
|         if (codeTextColor != 0) { | ||||
|             paint.setColor(codeTextColor); | ||||
|         } | ||||
| 
 | ||||
|         // custom typeface was set | ||||
|         if (codeTypeface != null) { | ||||
|             paint.setTypeface(codeTypeface); | ||||
|             if (codeTextSize != 0) { | ||||
|                 paint.setTextSize(codeTextSize); | ||||
|             } | ||||
|         } else { | ||||
|             paint.setTypeface(Typeface.MONOSPACE); | ||||
|             final float textSize; | ||||
|             if (codeTextSize != 0) { | ||||
|                 textSize = codeTextSize; | ||||
|             } else { | ||||
|                 textSize = paint.getTextSize() * CODE_DEF_TEXT_SIZE_RATIO; | ||||
|             } | ||||
|             paint.setTextSize(textSize); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public int getCodeMultilineMargin() { | ||||
|         return codeMultilineMargin; | ||||
|     } | ||||
| 
 | ||||
|     public int getCodeBackgroundColor(@NonNull Paint paint) { | ||||
|         final int color; | ||||
|         if (codeBackgroundColor != 0) { | ||||
|             color = codeBackgroundColor; | ||||
|         } else { | ||||
|             color = ColorUtils.applyAlpha(paint.getColor(), CODE_DEF_BACKGROUND_COLOR_ALPHA); | ||||
|         } | ||||
|         return color; | ||||
|     } | ||||
| 
 | ||||
|     public void applyHeadingTextStyle(@NonNull Paint paint, @IntRange(from = 1, to = 6) int level) { | ||||
|         paint.setFakeBoldText(true); | ||||
|         paint.setTextSize(paint.getTextSize() * HEADING_SIZES[level - 1]); | ||||
|     } | ||||
| 
 | ||||
|     public void applyHeadingBreakStyle(@NonNull Paint paint) { | ||||
|         final int color; | ||||
|         if (headingBreakColor != 0) { | ||||
|             color = headingBreakColor; | ||||
|         } else { | ||||
|             color = ColorUtils.applyAlpha(paint.getColor(), HEADING_DEF_BREAK_COLOR_ALPHA); | ||||
|         } | ||||
|         paint.setColor(color); | ||||
|         paint.setStyle(Paint.Style.FILL); | ||||
|         if (headingBreakHeight != 0) { | ||||
|             //noinspection SuspiciousNameCombination | ||||
|             paint.setStrokeWidth(headingBreakHeight); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public void applySuperScriptStyle(@NonNull TextPaint paint) { | ||||
|         final float ratio; | ||||
|         if (Float.compare(scriptTextSizeRatio, .0F) == 0) { | ||||
|             ratio = SCRIPT_DEF_TEXT_SIZE_RATIO; | ||||
|         } else { | ||||
|             ratio = scriptTextSizeRatio; | ||||
|         } | ||||
|         paint.setTextSize(paint.getTextSize() * ratio); | ||||
|         paint.baselineShift += (int) (paint.ascent() / 2); | ||||
|     } | ||||
| 
 | ||||
|     public void applySubScriptStyle(@NonNull TextPaint paint) { | ||||
|         final float ratio; | ||||
|         if (Float.compare(scriptTextSizeRatio, .0F) == 0) { | ||||
|             ratio = SCRIPT_DEF_TEXT_SIZE_RATIO; | ||||
|         } else { | ||||
|             ratio = scriptTextSizeRatio; | ||||
|         } | ||||
|         paint.setTextSize(paint.getTextSize() * ratio); | ||||
|         paint.baselineShift -= (int) (paint.ascent() / 2); | ||||
|     } | ||||
| 
 | ||||
|     public void applyThematicBreakStyle(@NonNull Paint paint) { | ||||
|         final int color; | ||||
|         if (thematicBreakColor != 0) { | ||||
|             color = thematicBreakColor; | ||||
|         } else { | ||||
|             color = ColorUtils.applyAlpha(paint.getColor(), THEMATIC_BREAK_DEF_ALPHA); | ||||
|         } | ||||
|         paint.setColor(color); | ||||
|         paint.setStyle(Paint.Style.FILL); | ||||
| 
 | ||||
|         if (thematicBreakHeight != 0) { | ||||
|             //noinspection SuspiciousNameCombination | ||||
|             paint.setStrokeWidth(thematicBreakHeight); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public static class Builder { | ||||
| 
 | ||||
|         private int linkColor; | ||||
|         private int blockMargin; | ||||
|         private int blockQuoteWidth; | ||||
|         private int blockQuoteColor; | ||||
|         private int listItemColor; | ||||
|         private int bulletListItemStrokeWidth; | ||||
|         private int bulletWidth; | ||||
|         private int codeTextColor; | ||||
|         private int codeBackgroundColor; | ||||
|         private int codeMultilineMargin; | ||||
|         private Typeface codeTypeface; | ||||
|         private int codeTextSize; | ||||
|         private int headingBreakHeight; | ||||
|         private int headingBreakColor; | ||||
|         private float scriptTextSizeRatio; | ||||
|         private int thematicBreakColor; | ||||
|         private int thematicBreakHeight; | ||||
| 
 | ||||
|         Builder() { | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|         Builder(@NonNull SpannableTheme theme) { | ||||
| 
 | ||||
|             this.linkColor = theme.linkColor; | ||||
|             this.blockMargin = theme.blockMargin; | ||||
|             this.blockQuoteWidth = theme.blockQuoteWidth; | ||||
|             this.blockQuoteColor = theme.blockQuoteColor; | ||||
|             this.listItemColor = theme.listItemColor; | ||||
|             this.bulletListItemStrokeWidth = theme.bulletListItemStrokeWidth; | ||||
|             this.bulletWidth = theme.bulletWidth; | ||||
|             this.codeTextColor = theme.codeTextColor; | ||||
|             this.codeBackgroundColor = theme.codeBackgroundColor; | ||||
|             this.codeMultilineMargin = theme.codeMultilineMargin; | ||||
|             this.codeTypeface = theme.codeTypeface; | ||||
|             this.codeTextSize = theme.codeTextSize; | ||||
|             this.headingBreakHeight = theme.headingBreakHeight; | ||||
|             this.headingBreakColor = theme.headingBreakColor; | ||||
|             this.scriptTextSizeRatio = theme.scriptTextSizeRatio; | ||||
|             this.thematicBreakColor = theme.thematicBreakColor; | ||||
|             this.thematicBreakHeight = theme.thematicBreakHeight; | ||||
|         } | ||||
| 
 | ||||
|         public Builder linkColor(int linkColor) { | ||||
|             this.linkColor = linkColor; | ||||
|             return this; | ||||
|         } | ||||
| 
 | ||||
|         public Builder blockMargin(int blockMargin) { | ||||
|             this.blockMargin = blockMargin; | ||||
|             return this; | ||||
|         } | ||||
| 
 | ||||
|         public Builder blockQuoteWidth(int blockQuoteWidth) { | ||||
|             this.blockQuoteWidth = blockQuoteWidth; | ||||
|             return this; | ||||
|         } | ||||
| 
 | ||||
|         public Builder blockQuoteColor(int blockQuoteColor) { | ||||
|             this.blockQuoteColor = blockQuoteColor; | ||||
|             return this; | ||||
|         } | ||||
| 
 | ||||
|         public Builder listItemColor(int listItemColor) { | ||||
|             this.listItemColor = listItemColor; | ||||
|             return this; | ||||
|         } | ||||
| 
 | ||||
|         public Builder bulletListItemStrokeWidth(int bulletListItemStrokeWidth) { | ||||
|             this.bulletListItemStrokeWidth = bulletListItemStrokeWidth; | ||||
|             return this; | ||||
|         } | ||||
| 
 | ||||
|         public Builder bulletWidth(int bulletWidth) { | ||||
|             this.bulletWidth = bulletWidth; | ||||
|             return this; | ||||
|         } | ||||
| 
 | ||||
|         public Builder codeTextColor(int codeTextColor) { | ||||
|             this.codeTextColor = codeTextColor; | ||||
|             return this; | ||||
|         } | ||||
| 
 | ||||
|         public Builder codeBackgroundColor(int codeBackgroundColor) { | ||||
|             this.codeBackgroundColor = codeBackgroundColor; | ||||
|             return this; | ||||
|         } | ||||
| 
 | ||||
|         public Builder codeMultilineMargin(int codeMultilineMargin) { | ||||
|             this.codeMultilineMargin = codeMultilineMargin; | ||||
|             return this; | ||||
|         } | ||||
| 
 | ||||
|         public Builder codeTypeface(Typeface codeTypeface) { | ||||
|             this.codeTypeface = codeTypeface; | ||||
|             return this; | ||||
|         } | ||||
| 
 | ||||
|         public Builder codeTextSize(int codeTextSize) { | ||||
|             this.codeTextSize = codeTextSize; | ||||
|             return this; | ||||
|         } | ||||
| 
 | ||||
|         public Builder headingBreakHeight(int headingBreakHeight) { | ||||
|             this.headingBreakHeight = headingBreakHeight; | ||||
|             return this; | ||||
|         } | ||||
| 
 | ||||
|         public Builder headingBreakColor(int headingBreakColor) { | ||||
|             this.headingBreakColor = headingBreakColor; | ||||
|             return this; | ||||
|         } | ||||
| 
 | ||||
|         public Builder scriptTextSizeRatio(float scriptTextSizeRatio) { | ||||
|             this.scriptTextSizeRatio = scriptTextSizeRatio; | ||||
|             return this; | ||||
|         } | ||||
| 
 | ||||
|         public Builder thematicBreakColor(int thematicBreakColor) { | ||||
|             this.thematicBreakColor = thematicBreakColor; | ||||
|             return this; | ||||
|         } | ||||
| 
 | ||||
|         public Builder thematicBreakHeight(int thematicBreakHeight) { | ||||
|             this.thematicBreakHeight = thematicBreakHeight; | ||||
|             return this; | ||||
|         } | ||||
| 
 | ||||
|         public SpannableTheme build() { | ||||
|             return new SpannableTheme(this); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private static class Px { | ||||
|         private final float density; | ||||
| 
 | ||||
|         Px(@NonNull Context context) { | ||||
|             this.density = context.getResources().getDisplayMetrics().density; | ||||
|         } | ||||
| 
 | ||||
|         int px(int dp) { | ||||
|             return (int) (dp * density + .5F); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,28 @@ | ||||
| package ru.noties.markwon.spans; | ||||
| 
 | ||||
| import android.support.annotation.NonNull; | ||||
| import android.text.TextPaint; | ||||
| import android.text.style.MetricAffectingSpan; | ||||
| 
 | ||||
| public class SubScriptSpan extends MetricAffectingSpan { | ||||
| 
 | ||||
|     private final SpannableTheme theme; | ||||
| 
 | ||||
|     public SubScriptSpan(@NonNull SpannableTheme theme) { | ||||
|         this.theme = theme; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void updateDrawState(TextPaint tp) { | ||||
|         apply(tp); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void updateMeasureState(TextPaint tp) { | ||||
|         apply(tp); | ||||
|     } | ||||
| 
 | ||||
|     private void apply(TextPaint paint) { | ||||
|         theme.applySubScriptStyle(paint); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,28 @@ | ||||
| package ru.noties.markwon.spans; | ||||
| 
 | ||||
| import android.support.annotation.NonNull; | ||||
| import android.text.TextPaint; | ||||
| import android.text.style.MetricAffectingSpan; | ||||
| 
 | ||||
| public class SuperScriptSpan extends MetricAffectingSpan { | ||||
| 
 | ||||
|     private final SpannableTheme theme; | ||||
| 
 | ||||
|     public SuperScriptSpan(@NonNull SpannableTheme theme) { | ||||
|         this.theme = theme; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void updateDrawState(TextPaint tp) { | ||||
|         apply(tp); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void updateMeasureState(TextPaint tp) { | ||||
|         apply(tp); | ||||
|     } | ||||
| 
 | ||||
|     private void apply(TextPaint paint) { | ||||
|         theme.applySuperScriptStyle(paint); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,39 @@ | ||||
| package ru.noties.markwon.spans; | ||||
| 
 | ||||
| import android.graphics.Canvas; | ||||
| import android.graphics.Paint; | ||||
| import android.graphics.Rect; | ||||
| import android.support.annotation.NonNull; | ||||
| import android.text.Layout; | ||||
| import android.text.style.LeadingMarginSpan; | ||||
| 
 | ||||
| public class ThematicBreakSpan implements LeadingMarginSpan { | ||||
| 
 | ||||
|     private final SpannableTheme theme; | ||||
|     private final Rect rect = ObjectsPool.rect(); | ||||
|     private final Paint paint = ObjectsPool.paint(); | ||||
| 
 | ||||
|     public ThematicBreakSpan(@NonNull SpannableTheme theme) { | ||||
|         this.theme = theme; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public int getLeadingMargin(boolean first) { | ||||
|         return 0; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void drawLeadingMargin(Canvas c, Paint p, int x, int dir, int top, int baseline, int bottom, CharSequence text, int start, int end, boolean first, Layout layout) { | ||||
| 
 | ||||
|         final int middle = top + ((bottom - top) / 2); | ||||
| 
 | ||||
|         paint.set(p); | ||||
|         theme.applyThematicBreakStyle(paint); | ||||
| 
 | ||||
|         final int height = (int) (paint.getStrokeWidth() + .5F); | ||||
|         final int halfHeight = (int) (height / 2.F + .5F); | ||||
| 
 | ||||
|         rect.set(x, middle - halfHeight, c.getWidth(), middle + halfHeight); | ||||
|         c.drawRect(rect, paint); | ||||
|     } | ||||
| } | ||||
| @ -1,18 +0,0 @@ | ||||
| apply plugin: 'com.android.library' | ||||
| 
 | ||||
| android { | ||||
| 
 | ||||
|     compileSdkVersion TARGET_SDK | ||||
|     buildToolsVersion BUILD_TOOLS | ||||
| 
 | ||||
|     defaultConfig { | ||||
|         minSdkVersion MIN_SDK | ||||
|         targetSdkVersion TARGET_SDK | ||||
|         versionCode 1 | ||||
|         versionName version | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| dependencies { | ||||
|     compile SUPPORT_ANNOTATIONS | ||||
| } | ||||
| @ -1 +0,0 @@ | ||||
| <manifest package="ru.noties.markwon.spans" /> | ||||
| @ -1,84 +0,0 @@ | ||||
| package ru.noties.markwon.spans; | ||||
| 
 | ||||
| import android.graphics.Canvas; | ||||
| import android.graphics.Paint; | ||||
| import android.graphics.Rect; | ||||
| import android.support.annotation.ColorInt; | ||||
| import android.support.annotation.IntRange; | ||||
| import android.support.annotation.NonNull; | ||||
| import android.text.Layout; | ||||
| import android.text.style.LeadingMarginSpan; | ||||
| 
 | ||||
| public class BlockQuoteSpan implements LeadingMarginSpan { | ||||
| 
 | ||||
|     private static final int DEF_COLOR_ALPHA = 50; | ||||
| 
 | ||||
|     @SuppressWarnings("WeakerAccess") | ||||
|     public static class Config { | ||||
| 
 | ||||
|         final int totalWidth; | ||||
|         final int quoteWidth; // by default 1/4 of width | ||||
|         final int quoteColor; // by default textColor with 0.2 alpha | ||||
| 
 | ||||
|         public Config( | ||||
|                 @IntRange(from = 1) int totalWidth, | ||||
|                 @IntRange(from = 0) int quoteWidth, | ||||
|                 @ColorInt int quoteColor) { | ||||
|             this.totalWidth = totalWidth; | ||||
|             this.quoteWidth = quoteWidth; | ||||
|             this.quoteColor = quoteColor; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private final Config config; | ||||
|     private final Rect rect = ObjectsPool.rect(); | ||||
|     private final Paint paint = ObjectsPool.paint(); | ||||
|     private final int indent; | ||||
| 
 | ||||
|     public BlockQuoteSpan(@NonNull Config config, int indent) { | ||||
|         this.config = config; | ||||
|         this.indent = indent; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public int getLeadingMargin(boolean first) { | ||||
|         return config.totalWidth; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void drawLeadingMargin( | ||||
|             Canvas c, | ||||
|             Paint p, | ||||
|             int x, | ||||
|             int dir, | ||||
|             int top, | ||||
|             int baseline, | ||||
|             int bottom, | ||||
|             CharSequence text, | ||||
|             int start, | ||||
|             int end, | ||||
|             boolean first, | ||||
|             Layout layout) { | ||||
| 
 | ||||
|         final int width; | ||||
|         if (config.quoteWidth == 0) { | ||||
|             width = (int) (config.totalWidth / 4.F + .5F); | ||||
|         } else { | ||||
|             width = config.quoteWidth; | ||||
|         } | ||||
| 
 | ||||
|         final int color; | ||||
|         if (config.quoteColor != 0) { | ||||
|             color = config.quoteColor; | ||||
|         } else { | ||||
|             color = ColorUtils.applyAlpha(p.getColor(), DEF_COLOR_ALPHA); | ||||
|         } | ||||
|         paint.setStyle(Paint.Style.FILL); | ||||
|         paint.setColor(color); | ||||
| 
 | ||||
|         final int left = config.totalWidth * (indent - 1); | ||||
|         rect.set(left, top, left + width, bottom); | ||||
| 
 | ||||
|         c.drawRect(rect, paint); | ||||
|     } | ||||
| } | ||||
| @ -1,146 +0,0 @@ | ||||
| package ru.noties.markwon.spans; | ||||
| 
 | ||||
| import android.graphics.Canvas; | ||||
| import android.graphics.Paint; | ||||
| import android.graphics.Rect; | ||||
| import android.graphics.Typeface; | ||||
| import android.support.annotation.ColorInt; | ||||
| import android.support.annotation.IntRange; | ||||
| import android.support.annotation.NonNull; | ||||
| import android.text.Layout; | ||||
| import android.text.TextPaint; | ||||
| import android.text.style.LeadingMarginSpan; | ||||
| import android.text.style.MetricAffectingSpan; | ||||
| 
 | ||||
| public class CodeSpan extends MetricAffectingSpan implements LeadingMarginSpan { | ||||
| 
 | ||||
|     private static final int DEF_COLOR_ALPHA = 25; | ||||
| 
 | ||||
|     @SuppressWarnings("WeakerAccess") | ||||
|     public static class Config { | ||||
| 
 | ||||
|         public static Builder builder() { | ||||
|             return new Builder(); | ||||
|         } | ||||
| 
 | ||||
|         final int textColor; // by default the same as main text | ||||
|         final int backgroundColor; // by default textColor with 0.1 alpha | ||||
|         final int multilineMargin; // by default 0 | ||||
|         final int textSize; // by default the same as main text | ||||
|         final Typeface typeface; // by default Typeface.MONOSPACE | ||||
| 
 | ||||
|         private Config(Builder builder) { | ||||
|             this.textColor = builder.textColor; | ||||
|             this.backgroundColor = builder.backgroundColor; | ||||
|             this.multilineMargin = builder.multilineMargin; | ||||
|             this.textSize = builder.textSize; | ||||
|             this.typeface = builder.typeface; | ||||
|         } | ||||
| 
 | ||||
|         public static class Builder { | ||||
| 
 | ||||
|             int textColor; | ||||
|             int backgroundColor; | ||||
|             int multilineMargin; | ||||
|             int textSize; | ||||
|             Typeface typeface; | ||||
| 
 | ||||
|             public Builder setTextColor(@ColorInt int textColor) { | ||||
|                 this.textColor = textColor; | ||||
|                 return this; | ||||
|             } | ||||
| 
 | ||||
|             public Builder setBackgroundColor(@ColorInt int backgroundColor) { | ||||
|                 this.backgroundColor = backgroundColor; | ||||
|                 return this; | ||||
|             } | ||||
| 
 | ||||
|             public Builder setMultilineMargin(int multilineMargin) { | ||||
|                 this.multilineMargin = multilineMargin; | ||||
|                 return this; | ||||
|             } | ||||
| 
 | ||||
|             public Builder setTextSize(@IntRange(from = 0) int textSize) { | ||||
|                 this.textSize = textSize; | ||||
|                 return this; | ||||
|             } | ||||
| 
 | ||||
|             public Builder setTypeface(@NonNull Typeface typeface) { | ||||
|                 this.typeface = typeface; | ||||
|                 return this; | ||||
|             } | ||||
| 
 | ||||
|             public Config build() { | ||||
|                 if (typeface == null) { | ||||
|                     typeface = Typeface.MONOSPACE; | ||||
|                 } | ||||
|                 return new Config(this); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private final Config config; | ||||
|     private final Rect rect = ObjectsPool.rect(); | ||||
|     private final Paint paint = ObjectsPool.paint(); | ||||
| 
 | ||||
|     private final boolean multiline; | ||||
| 
 | ||||
|     public CodeSpan(@NonNull Config config, boolean multiline) { | ||||
|         this.config = config; | ||||
|         this.multiline = multiline; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void updateMeasureState(TextPaint p) { | ||||
|         apply(p); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void updateDrawState(TextPaint ds) { | ||||
|         apply(ds); | ||||
|         if (!multiline) { | ||||
|             final int color; | ||||
|             if (config.backgroundColor == 0) { | ||||
|                 color = ColorUtils.applyAlpha(ds.getColor(), DEF_COLOR_ALPHA); | ||||
|             } else { | ||||
|                 color = config.backgroundColor; | ||||
|             } | ||||
|             ds.bgColor = color; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private void apply(TextPaint p) { | ||||
|         p.setTypeface(config.typeface); | ||||
|         if (config.textSize > 0) { | ||||
|             p.setTextSize(config.textSize); | ||||
|         } | ||||
|         if (config.textColor != 0) { | ||||
|             p.setColor(config.textColor); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public int getLeadingMargin(boolean first) { | ||||
|         return multiline ? config.multilineMargin : 0; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void drawLeadingMargin(Canvas c, Paint p, int x, int dir, int top, int baseline, int bottom, CharSequence text, int start, int end, boolean first, Layout layout) { | ||||
| 
 | ||||
|         if (multiline) { | ||||
| 
 | ||||
|             final int color; | ||||
|             if (config.backgroundColor == 0) { | ||||
|                 color = ColorUtils.applyAlpha(p.getColor(), DEF_COLOR_ALPHA); | ||||
|             } else { | ||||
|                 color = config.backgroundColor; | ||||
|             } | ||||
|             paint.setStyle(Paint.Style.FILL); | ||||
|             paint.setColor(color); | ||||
| 
 | ||||
|             rect.set(x, top, c.getWidth(), bottom); | ||||
| 
 | ||||
|             c.drawRect(rect, paint); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -1,97 +0,0 @@ | ||||
| package ru.noties.markwon.spans; | ||||
| 
 | ||||
| import android.graphics.Canvas; | ||||
| import android.graphics.Paint; | ||||
| import android.graphics.Rect; | ||||
| import android.support.annotation.ColorInt; | ||||
| import android.support.annotation.IntRange; | ||||
| import android.support.annotation.NonNull; | ||||
| import android.text.Layout; | ||||
| import android.text.TextPaint; | ||||
| import android.text.style.LeadingMarginSpan; | ||||
| import android.text.style.MetricAffectingSpan; | ||||
| 
 | ||||
| public class HeadingSpan extends MetricAffectingSpan implements LeadingMarginSpan { | ||||
| 
 | ||||
|     // taken from html spec (most browsers render headings like that) | ||||
|     private static final float[] HEADING_SIZES = { | ||||
|             2.F, 1.5F, 1.17F, 1.F, .83F, .67F, | ||||
|     }; | ||||
| 
 | ||||
|     private static final int DEF_BREAK_COLOR_ALPHA = 127; | ||||
| 
 | ||||
|     public static class Config { | ||||
| 
 | ||||
|         final int breakHeight; // by default stroke width | ||||
|         final int breakColor; // by default -> textColor with 0.5 alpha | ||||
| 
 | ||||
|         public Config(@IntRange(from = 0) int breakHeight, @ColorInt int breakColor) { | ||||
|             this.breakHeight = breakHeight; | ||||
|             this.breakColor = breakColor; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private final Config config; | ||||
|     private final Rect rect = ObjectsPool.rect(); | ||||
|     private final Paint paint = ObjectsPool.paint(); | ||||
|     private final int level; | ||||
|     private final int end; | ||||
| 
 | ||||
|     public HeadingSpan(@NonNull Config config, @IntRange(from = 1, to = 6) int level, @IntRange(from = 0) int end) { | ||||
|         this.config = config; | ||||
|         this.level = level - 1; | ||||
|         this.end = end; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void updateMeasureState(TextPaint p) { | ||||
|         apply(p); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void updateDrawState(TextPaint tp) { | ||||
|         apply(tp); | ||||
|     } | ||||
| 
 | ||||
|     private void apply(TextPaint paint) { | ||||
|         paint.setTextSize(paint.getTextSize() * HEADING_SIZES[level]); | ||||
|         paint.setFakeBoldText(true); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public int getLeadingMargin(boolean first) { | ||||
|         // no margin actually, but we need to access Canvas to draw break | ||||
|         return 0; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void drawLeadingMargin(Canvas c, Paint p, int x, int dir, int top, int baseline, int bottom, CharSequence text, int start, int end, boolean first, Layout layout) { | ||||
| 
 | ||||
|         // if we are configured to draw underlines, draw them here | ||||
| 
 | ||||
|         if (level == 0 | ||||
|                 || level == 1) { | ||||
| 
 | ||||
|             if (this.end == end) { | ||||
| 
 | ||||
|                 final int color; | ||||
|                 final int breakHeight; | ||||
|                 if (config.breakColor == 0) { | ||||
|                     color = ColorUtils.applyAlpha(p.getColor(), DEF_BREAK_COLOR_ALPHA); | ||||
|                 } else { | ||||
|                     color = config.breakColor; | ||||
|                 } | ||||
|                 if (config.breakHeight == 0) { | ||||
|                     breakHeight = (int) (p.getStrokeWidth() + .5F); | ||||
|                 } else { | ||||
|                     breakHeight = config.breakHeight; | ||||
|                 } | ||||
|                 paint.setStyle(Paint.Style.FILL); | ||||
|                 paint.setColor(color); | ||||
| 
 | ||||
|                 rect.set(x, bottom - breakHeight, c.getWidth(), bottom); | ||||
|                 c.drawRect(rect, paint); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -1,19 +0,0 @@ | ||||
| package ru.noties.markwon.spans; | ||||
| 
 | ||||
| import android.text.TextPaint; | ||||
| import android.text.style.MetricAffectingSpan; | ||||
| 
 | ||||
| public class SubSpan extends MetricAffectingSpan { | ||||
| 
 | ||||
|     @Override | ||||
|     public void updateDrawState(TextPaint tp) { | ||||
|         tp.setTextSize(tp.getTextSize() * .75F); | ||||
|         tp.baselineShift -= (int) (tp.ascent() / 2); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void updateMeasureState(TextPaint tp) { | ||||
|         tp.setTextSize(tp.getTextSize() * .75F); | ||||
|         tp.baselineShift -= (int) (tp.ascent() / 2); | ||||
|     } | ||||
| } | ||||
| @ -1,19 +0,0 @@ | ||||
| package ru.noties.markwon.spans; | ||||
| 
 | ||||
| import android.text.TextPaint; | ||||
| import android.text.style.MetricAffectingSpan; | ||||
| 
 | ||||
| public class SupSpan extends MetricAffectingSpan { | ||||
| 
 | ||||
|     @Override | ||||
|     public void updateDrawState(TextPaint tp) { | ||||
|         tp.setTextSize(tp.getTextSize() * .75F); | ||||
|         tp.baselineShift += (int) (tp.ascent() / 2); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void updateMeasureState(TextPaint tp) { | ||||
|         tp.setTextSize(tp.getTextSize() * .75F); | ||||
|         tp.baselineShift += (int) (tp.ascent() / 2); | ||||
|     } | ||||
| } | ||||
| @ -1,64 +0,0 @@ | ||||
| package ru.noties.markwon.spans; | ||||
| 
 | ||||
| import android.graphics.Canvas; | ||||
| import android.graphics.Paint; | ||||
| import android.graphics.Rect; | ||||
| import android.support.annotation.ColorInt; | ||||
| import android.support.annotation.IntRange; | ||||
| import android.text.Layout; | ||||
| import android.text.style.LeadingMarginSpan; | ||||
| 
 | ||||
| public class ThematicBreakSpan implements LeadingMarginSpan { | ||||
| 
 | ||||
|     private static final int DEF_COLOR_ALPHA = 127; | ||||
| 
 | ||||
|     public static class Config { | ||||
| 
 | ||||
|         final int color; // by default textColor with 0.5 alpha | ||||
|         final int height; // by default strokeWidth of paint | ||||
| 
 | ||||
|         public Config(@ColorInt int color, @IntRange(from = 0) int height) { | ||||
|             this.color = color; | ||||
|             this.height = height; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private final Config config; | ||||
|     private final Rect rect = ObjectsPool.rect(); | ||||
|     private final Paint paint = ObjectsPool.paint(); | ||||
| 
 | ||||
|     public ThematicBreakSpan(Config config) { | ||||
|         this.config = config; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public int getLeadingMargin(boolean first) { | ||||
|         return 0; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void drawLeadingMargin(Canvas c, Paint p, int x, int dir, int top, int baseline, int bottom, CharSequence text, int start, int end, boolean first, Layout layout) { | ||||
| 
 | ||||
|         final int middle = top + ((bottom - top) / 2); | ||||
| 
 | ||||
|         final int color; | ||||
|         if (config.color == 0) { | ||||
|             color = ColorUtils.applyAlpha(p.getColor(), DEF_COLOR_ALPHA); | ||||
|         } else { | ||||
|             color = config.color; | ||||
|         } | ||||
|         paint.setColor(color); | ||||
|         paint.setStyle(Paint.Style.FILL); | ||||
| 
 | ||||
|         final int height; | ||||
|         if (config.height == 0) { | ||||
|             height = (int) (p.getStrokeWidth() + .5F); | ||||
|         } else { | ||||
|             height = config.height; | ||||
|         } | ||||
|         final int halfHeight = (int) (height / 2.F + .5F); | ||||
| 
 | ||||
|         rect.set(x, middle - halfHeight, c.getWidth(), middle + halfHeight); | ||||
|         c.drawRect(rect, paint); | ||||
|     } | ||||
| } | ||||
| @ -1 +1 @@ | ||||
| include ':app', ':library-spans', ':library-renderer', ':library-view' | ||||
| include ':app', ':library-renderer', ':library-view' | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Dimitry Ivanov
						Dimitry Ivanov