1 /*
2  * Copyright (C) 2006 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.text.style;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Px;
21 import android.graphics.Canvas;
22 import android.graphics.Paint;
23 import android.graphics.drawable.Drawable;
24 import android.text.Layout;
25 import android.text.Spanned;
26 
27 /**
28  * A span which adds a drawable and a padding to the paragraph it's attached to.
29  * <p>
30  * If the height of the drawable is bigger than the height of the line it's attached to then the
31  * line height is increased to fit the drawable. <code>DrawableMarginSpan</code> allows setting a
32  * padding between the drawable and the text. The default value is 0. The span must be set from the
33  * beginning of the text, otherwise either the span won't be rendered or it will be rendered
34  * incorrectly.
35  * <p>
36  * For example, a drawable and a padding of 20px can be added like this:
37  * <pre>{@code SpannableString string = new SpannableString("Text with a drawable.");
38  * string.setSpan(new DrawableMarginSpan(drawable, 20), 0, string.length(),
39  * Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);}</pre>
40  * <img src="{@docRoot}reference/android/images/text/style/drawablemarginspan.png" />
41  * <figcaption>Text with a drawable and a padding.</figcaption>
42  * <p>
43  *
44  * @see IconMarginSpan for working with a {@link android.graphics.Bitmap} instead of
45  * a {@link Drawable}.
46  */
47 public class DrawableMarginSpan implements LeadingMarginSpan, LineHeightSpan {
48     private static final int STANDARD_PAD_WIDTH = 0;
49 
50     @NonNull
51     private final Drawable mDrawable;
52     @Px
53     private final int mPad;
54 
55     /**
56      * Creates a {@link DrawableMarginSpan} from a {@link Drawable}. The pad width will be 0.
57      *
58      * @param drawable the drawable to be added
59      */
DrawableMarginSpan(@onNull Drawable drawable)60     public DrawableMarginSpan(@NonNull Drawable drawable) {
61         this(drawable, STANDARD_PAD_WIDTH);
62     }
63 
64     /**
65      * Creates a {@link DrawableMarginSpan} from a {@link Drawable} and a padding, in pixels.
66      *
67      * @param drawable the drawable to be added
68      * @param pad      the distance between the drawable and the text
69      */
DrawableMarginSpan(@onNull Drawable drawable, int pad)70     public DrawableMarginSpan(@NonNull Drawable drawable, int pad) {
71         mDrawable = drawable;
72         mPad = pad;
73     }
74 
75     @Override
getLeadingMargin(boolean first)76     public int getLeadingMargin(boolean first) {
77         return mDrawable.getIntrinsicWidth() + mPad;
78     }
79 
80     @Override
drawLeadingMargin(@onNull Canvas c, @NonNull Paint p, int x, int dir, int top, int baseline, int bottom, @NonNull CharSequence text, int start, int end, boolean first, @NonNull Layout layout)81     public void drawLeadingMargin(@NonNull Canvas c, @NonNull Paint p, int x, int dir,
82             int top, int baseline, int bottom,
83             @NonNull CharSequence text, int start, int end,
84             boolean first, @NonNull Layout layout) {
85         int st = ((Spanned) text).getSpanStart(this);
86         int ix = (int) x;
87         int itop = (int) layout.getLineTop(layout.getLineForOffset(st));
88 
89         int dw = mDrawable.getIntrinsicWidth();
90         int dh = mDrawable.getIntrinsicHeight();
91 
92         // XXX What to do about Paint?
93         mDrawable.setBounds(ix, itop, ix + dw, itop + dh);
94         mDrawable.draw(c);
95     }
96 
97     @Override
chooseHeight(@onNull CharSequence text, int start, int end, int istartv, int v, @NonNull Paint.FontMetricsInt fm)98     public void chooseHeight(@NonNull CharSequence text, int start, int end,
99             int istartv, int v,
100             @NonNull Paint.FontMetricsInt fm) {
101         if (end == ((Spanned) text).getSpanEnd(this)) {
102             int ht = mDrawable.getIntrinsicHeight();
103 
104             int need = ht - (v + fm.descent - fm.ascent - istartv);
105             if (need > 0) {
106                 fm.descent += need;
107             }
108 
109             need = ht - (v + fm.bottom - fm.top - istartv);
110             if (need > 0) {
111                 fm.bottom += need;
112             }
113         }
114     }
115 
116     @Override
toString()117     public String toString() {
118         return "DrawableMarginSpan{drawable=" + mDrawable + ", padding=" + mPad + '}';
119     }
120 
121     /**
122      * Returns the drawable used.
123      * @return a drawable
124      */
getDrawable()125     @NonNull public Drawable getDrawable() {
126         return mDrawable;
127     }
128 
129     /**
130      * Returns a distance between the drawable and text in pixel.
131      * @return a distance pixel from the text
132      */
getPadding()133     @Px public int getPadding() {
134         return mPad;
135     }
136 }
137